Introduction & Setup
What is C?
C is a powerful general-purpose, procedural programming language developed by Dennis Ritchie at Bell Labs in 1972. It was designed to build the Unix operating system and has since become one of the most widely used and influential languages in computer science, serving as the foundation for languages like C++, Java, C#, and Python.
- Procedural Language: Instructions are executed step-by-step in a logical flow.
- Close to Hardware: Direct memory manipulation through pointers.
- Fast & Compact: Minimal runtime library, producing extremely fast machine code.
- Portable: Code compiled on one system can run on another with minimal modifications.
Setting Up the GCC Compiler
To compile C programs, you need a C compiler. The most common compiler is **GCC** (GNU Compiler Collection), which runs natively on Linux, macOS (via Xcode Command Line Tools), and Windows (via MinGW).
Your First C Program
Here is a basic C program that outputs "Hello, World!" to the screen:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
Write, save, and compile hello.c in your terminal using the command gcc hello.c -o hello, and execute it by running ./hello.
Variables & Data Types
Variable Declarations
C requires all variables to be declared with their specific type before they are used. Variables reserve a specific size of memory depending on their data type.
Basic Data Types in C
int: Stores whole numbers (integers), e.g.45.float: Stores decimal numbers (single precision), e.g.3.14f.double: Stores larger decimals (double precision), e.g.2.7182818.char: Stores single characters enclosed in single quotes, e.g.'X'.
Format Specifiers
C uses format specifiers within formatting strings to print or read data using printf and scanf: %d for int, %f for float, %lf for double, and %c for char.
#include <stdio.h>
int main() {
int age = 30;
float price = 5.99f;
char grade = 'A';
printf("Age: %d, Price: %.2f, Grade: %c\n", age, price, grade);
return 0;
}
Write a program that prompts the user for their age using scanf, increments the age by 5, and prints the result.
Operators & Expressions
Types of Operators
- Arithmetic:
+,-,*,/(integer division truncates decimal values if both operands are integers),%(modulo). - Relational: Compare operands and return 1 (true) or 0 (false), e.g.,
==,!=,<,>,<=,>=. - Logical: Combine relational conditions, e.g.,
&&(AND),||(OR),!(NOT). - Assignment: Assign value to variable:
=,+=,-=,*=,/=.
#include <stdio.h>
int main() {
int x = 10;
int y = 4;
int div = x / y; // Output: 2 (truncated)
int mod = x % y; // Output: 2 (remainder)
int active = (x > 5) && (y < 10); // True (1)
printf("Div: %d, Mod: %d, Active: %d\n", div, mod, active);
return 0;
}
Write a program that takes an integer and prints 1 if it is divisible by 5 and 7, and 0 otherwise.
Control Flow
Conditional Branching
Control flow allows execution branches based on true or false conditions. C uses if, else if, and else.
Switch Statement
The switch statement checks a variable against multiple integer or character values. Each case must terminate with a break statement to prevent falling through to the next case.
#include <stdio.h>
int main() {
int rating = 4;
if (rating >= 4) {
printf("Excellent!\n");
} else if (rating >= 2) {
printf("Average.\n");
} else {
printf("Poor.\n");
}
return 0;
}
Write a calculator program using a switch statement that reads two float numbers and a char operator (+, -, *, /), computes the operation, and prints the result.
Loops & Iteration
Loop Constructs
while: Evaluates condition before running the loop.do-while: Runs loop body once, then evaluates condition. Always runs at least once.for: Bundles init, condition, and increment step together. Good for counted loops.
Break & Continue
The break keyword terminates the current loop immediately. The continue keyword skips the remaining statements in the current iteration and jumps directly to the next loop evaluation.
#include <stdio.h>
int main() {
// For loop skipping 3
for (int i = 1; i <= 5; i++) {
if (i == 3) continue;
printf("%d ", i); // Outputs: 1 2 4 5
}
printf("\n");
// While loop with break
int num = 0;
while (1) {
if (num >= 3) break;
printf("%d\n", num);
num++;
}
return 0;
}
Write a program that uses a loop to print the multiplication table of a user-input integer up to 10.
Functions & Scope
Creating Functions
Functions divide a program into smaller, logical sub-units. A function must declare its return type, parameter list, and must return a matching value (unless the return type is void). If the function is defined after `main`, it must have a **forward declaration / prototype** at the top of the file.
Scope & Parameter Passing
In C, arguments are passed to functions **by value** (copies are made). Local variables are only visible inside their declared functions. Global variables are declared outside all functions and are accessible anywhere.
#include <stdio.h>
// Prototype declaration
int get_square(int num);
int main() {
int x = 4;
int square = get_square(x);
printf("Square of %d is %d\n", x, square); // 16
return 0;
}
// Definition
int get_square(int num) {
return num * num;
}
Write a recursive function factorial(int n) that computes and returns the factorial of an integer. Test it inside your main function.
Arrays & Matrices
Single-Dimensional Arrays
An array is a fixed-size, sequential list of variables sharing the same type. C arrays are zero-indexed, meaning elements are accessed from 0 up to size - 1.
Multi-Dimensional Arrays (Matrices)
You can create multi-dimensional arrays, which are frequently used to represent mathematical grids or matrices (e.g., rows and columns).
#include <stdio.h>
int main() {
// 1D Array
int numbers[5] = {1, 2, 3, 4, 5};
printf("Third element: %d\n", numbers[2]); // 3
// 2D Matrix (2 rows, 3 columns)
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printf("Element at row 1, col 2: %d\n", matrix[1][2]); // 6
return 0;
}
C does not perform runtime array bounds checking! Accessing indices outside the array bounds (e.g., index 5 on an array of size 5) leads to **undefined behavior** and can corrupt system memory.
Write a program that declares a 3x3 matrix, prompts the user to input integer values for all elements, and then computes the sum of the primary diagonal elements.
Pointers & Memory
Pointers
A pointer is a variable that stores the memory address of another variable. We declare a pointer by appending * to its data type. We get the address of a variable using the address-of operator (&), and access the value at the address using the dereference operator (*).
Pointer Arithmetic & Array Links
The name of an array acts as a constant pointer to its first element. Pointers can be incremented (ptr++) to point to subsequent elements in memory.
#include <stdio.h>
int main() {
int var = 10;
int *ptr = &var; // ptr stores address of var
printf("Address: %p\n", (void*)ptr);
printf("Value: %d\n", *ptr); // 10
*ptr = 20; // Modify var indirectly
printf("Modified value: %d\n", var); // 20
return 0;
}
Write a function swap(int *a, int *b) that uses pointers to swap the values of two integers. Call it from your main function and print the results.
Strings & Character Arrays
Strings in C
C does not have a native string data type. Instead, strings are represented as 1D arrays of type char that terminate with a special null character ('\0'). Always reserve one extra space in character arrays for this null terminator.
Standard String Functions
The <string.h> library contains functions to manipulate strings:
strlen(str): Returns string length (excluding the null character).strcpy(dest, src): Copies a source string to a destination buffer.strcat(dest, src): Appends source string to destination.strcmp(str1, str2): Compares strings alphabetically (returns 0 if equal).
#include <stdio.h>
#include <string.h>
int main() {
char greeting[20] = "Hello";
char name[] = " Alice";
strcat(greeting, name); // Concatenate
printf("%s\n", greeting); // Outputs: Hello Alice
printf("Length: %lu\n", strlen(greeting)); // 11
return 0;
}
Write a program that reads a string from the user using fgets, reverses it in place, and prints the reversed string.
Structures & Unions
Structures (struct)
A structure is a user-defined data type that allows you to group variables of different data types under a single name. Members are accessed using the dot (.) operator, or the arrow (->) operator when accessing structure variables through pointers.
Unions (union)
A union is similar to a structure, but it shares a single memory location for all its members. Only one member can hold a value at any given time.
#include <stdio.h>
#include <string.h>
struct Employee {
char name[50];
int id;
float salary;
};
int main() {
struct Employee emp1;
emp1.id = 1001;
emp1.salary = 75000.50f;
strcpy(emp1.name, "Bob Smith");
struct Employee *ptr = &emp1;
printf("Name: %s, Salary: %.2f\n", ptr->name, ptr->salary);
return 0;
}
Define a structure called Point representing 2D space coordinates (x, y). Write a function that calculates the Euclidean distance between two Points passed to it, and call it from your main loop.
Dynamic Memory Allocation
Dynamic Memory Functions
C allocates variables automatically on the stack. For runtime-defined memory requirements, C lets you allocate memory from the heap dynamically using the <stdlib.h> library:
malloc(size): Allocates a block of raw uninitialized memory.calloc(num, size): Allocates memory and initializes all bytes to zero.realloc(ptr, size): Resizes previously allocated heap blocks.free(ptr): Releases heap memory blocks back to the system.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*)malloc(3 * sizeof(int));
if (arr == NULL) return 1; // Check allocation
arr[0] = 10; arr[1] = 20; arr[2] = 30;
printf("Second value: %d\n", arr[1]);
free(arr); // Free memory block
arr = NULL;
return 0;
}
Always release dynamically allocated memory blocks using free(ptr) when you are done with them. Forgetting to release memory creates **memory leaks**, which consume system resources and can crash the computer.
Write a program that dynamically allocates an integer array of size input by the user, reads values for all indexes, prints them, and frees the memory block.
Preprocessor Directives
Macros and File Inclusions
The preprocessor processes source code *before* it is passed to the compiler. Directives start with the hash character (#):
#include: Inserts header contents directly into the file.#define: Declares macros (symbolic constants or inline replacement code).#ifdef/#ifndef/#endif: Controls conditional compilation of blocks.
#include <stdio.h>
#define PI 3.14159
#define AREA(r) (PI * (r) * (r))
int main() {
float r = 5.0f;
printf("Area: %.4f\n", AREA(r));
return 0;
}
Write a macro MAX(a, b) that evaluates to the maximum of two numbers using the ternary conditional operator. Test it in a C program.
File Handling
File Stream Operations
File handling in C is processed using the FILE structure defined in <stdio.h>. We manipulate files using file pointers (FILE *fp):
fopen(name, mode): Opens a file stream (modes:"r","w","a").fclose(fp): Closes open file streams.fprintf(fp, ...)/fscanf(fp, ...): Format print or write/read from file streams.fgetc/fputc: Reads/writes single characters.
#include <stdio.h>
int main() {
// Write
FILE *fp = fopen("output.txt", "w");
if (fp != NULL) {
fprintf(fp, "C File Handling Example.\n");
fclose(fp);
}
// Read
fp = fopen("output.txt", "r");
char buffer[100];
if (fp != NULL) {
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
}
return 0;
}
Write a program that copies the contents of a text file named source.txt to a new file named destination.txt character-by-character.
Storage Classes
Variable Lifetimes
Storage classes define the scope, visibility, and lifetime of variables in C:
auto: Default storage class for local variables.register: Requests the compiler to store variables in registers for speed.static: Retains variable values across function calls; local to compile unit.extern: Points to variables declared in other source files (global link).
#include <stdio.h>
void counter_func() {
static int count = 0; // Initialized once, retains value
count++;
printf("Count: %d\n", count);
}
int main() {
counter_func(); // Count: 1
counter_func(); // Count: 2
counter_func(); // Count: 3
return 0;
}
Write a program with a static variable inside a recursive function to count the number of recursive cycles completed.
Bitwise & Type Casting
Bitwise Operators
Bitwise operators perform calculations directly on the binary representation of integers:
&Bitwise AND,|Bitwise OR^Bitwise XOR,~Bitwise NOT (One's Complement)<<Left Shift (multiplies by 2),>>Right Shift (divides by 2)
Type Casting
Type casting converts variables of one type to another. We perform manual typecasting by prefixing variable names with desired types inside parentheses (e.g. (float)val).
#include <stdio.h>
int main() {
// Bitwise shift
unsigned char x = 5; // Binary: 00000101
printf("5 << 1: %d\n", x << 1); // 10 (Binary: 00001010)
// Type casting
int sum = 17;
int count = 5;
double mean = (double)sum / count; // 3.4
printf("Mean: %.1f\n", mean);
return 0;
}
Write a C program that checks if a user-input number is odd or even using the bitwise AND operator (&) instead of the modulus operator.
Final Capstone Project
Project: Dynamic Inventory Database
Consolidate your C programming skills by building a command-line Inventory Records system. The project utilizes structures, pointers, dynamic memory allocation, files, and format parsing.
Project Requirements:
- Item Structure: Defines Item containing attributes: name, ID, price, and stock count.
- Dynamic List: Manage items inside a dynamically allocated array. Reallocate array sizes if new items exceed capacity (via
realloc). - File Operations: Load records from a text file (
inventory.txt) on startup, and save all records to the file upon exiting. - Interactive Menu: Provide options to:
- Display all items
- Add new item record
- Update stock count of item
- Save inventory data and Exit
- Safe operations: Perform input checking to handle null pointer errors or invalid menu option values.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int id;
char name[30];
float price;
int stock;
} Item;
// Global list pointer and tracking variables
Item *inventory = NULL;
int item_count = 0;
int capacity = 2;
// Implement dynamic additions, displays, and main menu loop
Write the complete script in a file named inventory.c. Compile, run, add several items, check if the save file persists correctly, and free all heap memory before exiting.