0% found this document useful (0 votes)
5 views

Mastering C Language 2

The document provides comprehensive notes on mastering the C programming language, covering topics such as its introduction, operators, control structures, and loops. Each module includes objectives, key concepts, examples, and practice exercises to enhance understanding. It serves as a structured guide for learners to grasp the fundamentals of C programming effectively.

Uploaded by

bdsingh9040
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views

Mastering C Language 2

The document provides comprehensive notes on mastering the C programming language, covering topics such as its introduction, operators, control structures, and loops. Each module includes objectives, key concepts, examples, and practice exercises to enhance understanding. It serves as a structured guide for learners to grasp the fundamentals of C programming effectively.

Uploaded by

bdsingh9040
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 93

Mastering C Language – Full Course Notes

Table of Contents
1. Module 1: Introduction to C Language
2. Module 2: Operators and Expressions
3. Module 3: Control Structures and Loops
4. Module 4: Functions and Recursion
5. Module 5: Arrays and Strings
6. Module 6: Pointers and Dynamic Memory Allocation
7. Module 7: Structures and Unions
8. Module 8: File Handling
9. Module 9: Preprocessors and Macros
10. Module 10: Data Structures and Algorithms

Module 1: Introduction to C Language


Duration: 1 Week
Objective: Understand the basics of C, its history, syntax, and fundamental components.

Topics Covered:

1.1 History and Overview


1.2 Structure of a C Program
1.3 Basic Syntax and Keywords
1.4 Data Types and Variables
1.5 Input and Output Functions (printf, scanf)
1.6 Comments and Code Formatting

1.1 History and Overview

• Developed by Dennis Ritchie at Bell Labs in the early 1970s.


• Created to develop the UNIX operating system.
• Influenced by B Language and BCPL.
• Influenced modern languages such as C++, Java, C#, and Python.

Key Features of C:

1
• Structured Programming – Organized into functions.
• Low-Level Access – Using pointers for memory manipulation.
• Portability – Write once, compile anywhere.
• Speed and Efficiency – Compiles directly to machine code.
• Rich Library Support – Standard libraries for input, output, and memory management.

1.2 Structure of a C Program

• Preprocessor Directives – #include for header files.


• main() Function – Entry point of execution.
• Variable Declarations – Declaring variables before using them.
• Statements and Expressions – Logic and calculations.
• Return Statement – Ends the program.

Example:

#include <stdio.h>

int main() {
int num = 10;
printf("Number: %d\n", num);
return 0;
}

1.3 Basic Syntax and Keywords

• Case-Sensitive – Var and var are different.


• Statements end with a semicolon (;).
• Curly Braces ({}) group statements into blocks.
• Whitespace (spaces, tabs) is ignored but improves readability.
• Keywords in C: Reserved words like int, float, char, if, else, while, return, void, break,
continue.

1.4 Data Types and Variables

• Basic Data Types:

Data Type Description Size (Bytes) Format Specifier


int Integer numbers 4 %d or %i
float Single precision float 4 %f
double Double precision float 8 %lf
char Single character 1 %c
void No value 0 N/A

• Variable Declaration and Initialization:


• int age = 25;
• float salary = 50000.50;
• char grade = 'A';

2
1.5 Input and Output Functions

• printf() Function – Displays output on the screen.


• scanf() Function – Takes input from the user.

Example:

#include <stdio.h>

int main() {
int age;
printf("Enter your age: ");
scanf("%d", &age);
printf("You are %d years old.\n", age);
return 0;
}

1.6 Comments and Code Formatting

• Single-line Comment: //
• Multi-line Comment: /* ... */

Good Practices:

• Proper indentation improves readability.


• Use meaningful variable names.

Summary for Module 1:

• C is a powerful and structured language.


• Case-sensitive and follows strict syntax rules.
• Variables must be declared before use.
• printf() and scanf() are essential for I/O operations.
• Comments and proper formatting enhance code readability.

Module 2: Operators and Expressions

Duration: 1 Week
Objective: Understand how to perform operations and form expressions using C operators.

Topics Covered:

2.1 Arithmetic Operators (+, -, *, /, %)


2.2 Relational Operators (<, >, <=, >=, ==, !=)
2.3 Logical Operators (&&, ||, !)
2.4 Bitwise Operators (&, |, ^, ~, <<, >>)
2.5 Assignment Operators (=, +=, -=, etc.)

3
2.6 Conditional Operator (?:)
2.7 Operator Precedence and Associativity

2.1 Arithmetic Operators

• Used for basic mathematical operations.


• Operators:
o + Addition
o - Subtraction
o * Multiplication
o / Division
o % Modulus (Remainder of division)

Example:

#include <stdio.h>

int main() {
int a = 10, b = 3;
printf("Sum: %d\n", a + b); // Output: 13
printf("Difference: %d\n", a - b); // Output: 7
printf("Product: %d\n", a * b); // Output: 30
printf("Quotient: %d\n", a / b); // Output: 3
printf("Remainder: %d\n", a % b); // Output: 1
return 0;
}

Key Points:

• Division of two integers yields an integer result (no decimal).


• % operator cannot be used with float or double types.

Practice Exercise:

• Write a program to calculate the area and circumference of a circle using arithmetic operators.

2.2 Relational Operators

• Used to compare values.


• Operators:
o < Less than
o > Greater than
o <= Less than or equal to
o >= Greater than or equal to
o == Equal to
o != Not equal to

Example:

#include <stdio.h>

int main() {
int a = 5, b = 10;

4
printf("%d\n", a > b); // 0 (false)
printf("%d\n", a < b); // 1 (true)
printf("%d\n", a == b); // 0 (false)
printf("%d\n", a != b); // 1 (true)
return 0;
}

Key Points:

• Relational expressions return 1 (true) or 0 (false).


• Useful in conditional statements and loops.

Practice Exercise:

• Write a program to check if a number is even or odd using relational operators.

2.3 Logical Operators

• Used to combine multiple conditions.


• Operators:
o && Logical AND (True if both conditions are true)
o || Logical OR (True if at least one condition is true)
o ! Logical NOT (Reverses the condition)

Example:

#include <stdio.h>

int main() {
int a = 5, b = 10;
printf("%d\n", (a > 0) && (b > 0)); // 1 (true)
printf("%d\n", (a > 10) || (b > 0)); // 1 (true)
printf("%d\n", !(a > 10)); // 1 (true)
return 0;
}

Key Points:

• && has higher precedence than ||.


• ! has the highest precedence among logical operators.

Practice Exercise:

• Write a program to check if a number is within a given range using logical operators.

2.4 Bitwise Operators

• Operate on bits of data, mainly used in low-level programming.


• Operators:
o & Bitwise AND
o | Bitwise OR
o ^ Bitwise XOR
o ~ Bitwise NOT (One's Complement)

5
o << Left Shift
o >> Right Shift

Example:

#include <stdio.h>

int main() {
int a = 5, b = 3;
printf("a & b = %d\n", a & b); // Output: 1
printf("a | b = %d\n", a | b); // Output: 7
printf("a ^ b = %d\n", a ^ b); // Output: 6
printf("~a = %d\n", ~a); // Output: -6
printf("a << 1 = %d\n", a << 1); // Output: 10
printf("a >> 1 = %d\n", a >> 1); // Output: 2
return 0;
}

Key Points:

• Bitwise operations are faster and used in system-level programming.


• << and >> are used for efficient multiplication and division by powers of 2.

Practice Exercise:

• Implement a program to check if a number is even or odd using bitwise AND.

2.5 Assignment Operators

• Used to assign values to variables.


• Operators:
o = Simple Assignment
o += Addition Assignment
o -= Subtraction Assignment
o *= Multiplication Assignment
o /= Division Assignment
o %= Modulus Assignment

Example:

#include <stdio.h>

int main() {
int a = 10;
a += 5; // a = a + 5
printf("a = %d\n", a); // Output: 15
a *= 2; // a = a * 2
printf("a = %d\n", a); // Output: 30
return 0;
}

Practice Exercise:

• Create a program that calculates the compound interest using compound assignment operators.

6
2.6 Conditional Operator (?:)

• Short form of if-else statement.


• Syntax:
• condition ? expression_if_true : expression_if_false;

Example:

#include <stdio.h>

int main() {
int a = 10, b = 20;
int max = (a > b) ? a : b;
printf("Maximum: %d\n", max); // Output: 20
return 0;
}

2.7 Operator Precedence and Associativity

• Determines the order in which operators are evaluated.


• Example:
o *, /, and % have higher precedence than + and -.
o && has higher precedence than ||.
o = has the lowest precedence.

Example:

int result = 3 + 4 * 2; // Output: 11 (4*2 is evaluated first)

Summary for Module 2:

• C provides a variety of operators for arithmetic, relational, logical, and bitwise operations.
• Understanding operator precedence is essential for writing correct expressions.
• The conditional operator (?:) is a shorthand for simple if-else statements.

Alright! Let's continue with the remaining Looping Statements and then move on to Nested Loops and
Jump Statements in Module 3: Control Structures and Loops. Each concept will be explained with clear
examples and coding exercises to ensure you grasp the fundamentals thoroughly.

Module 3: Control Structures and Loops


Duration: 1 Week
Objective: Learn to control the flow of execution using conditional statements and loops.

Topics Covered:

7
3.1 Conditional Statements

• 3.1.1 if Statement
• 3.1.2 if-else Statement
• 3.1.3 if-else if-else Ladder
• 3.1.4 switch Statement

3.2 Looping Statements

• 3.2.1 for Loop


• 3.2.2 while Loop
• 3.2.3 do-while Loop

3.3 Nested Loops and Conditional Statements


3.4 Jump Statements

• 3.4.1 break Statement


• 3.4.2 continue Statement
• 3.4.3 goto Statement

Alright! Let's continue with Module 3: Control Structures and Loops. This module covers all the essential
control statements in C that help in decision-making, looping, and altering the flow of execution. I'll explain
each topic with clear explanations, practical examples, and coding exercises to enhance your
understanding.

Module 3: Control Structures and Loops


Duration: 1 Week
Objective: Learn to control the flow of execution using conditional statements and loops.

Topics Covered:

3.1 Conditional Statements

• 3.1.1 if Statement
• 3.1.2 if-else Statement
• 3.1.3 if-else if-else Ladder
• 3.1.4 switch Statement

3.2 Looping Statements

• 3.2.1 for Loop


• 3.2.2 while Loop
• 3.2.3 do-while Loop

3.3 Nested Loops and Conditional Statements


3.4 Jump Statements

• 3.4.1 break Statement


• 3.4.2 continue Statement

8
• 3.4.3 goto Statement

3.1 Conditional Statements

Conditional statements allow programs to make decisions and execute certain blocks of code based on
specified conditions.

3.1.1 if Statement

• Executes a block of code if the condition is true.


• Syntax:
• if (condition) {
• // Code to execute if condition is true
• }

Example:

#include <stdio.h>

int main() {
int num = 10;
if (num > 5) {
printf("Number is greater than 5\n");
}
return 0;
}

Output:

Number is greater than 5

Key Points:

• If the condition is false, the block is skipped.


• Only one block is executed per if statement.

Practice Exercise:

• Write a program to check if a number is positive or negative using if statement.

3.1.2 if-else Statement

• Provides an alternative block of code if the condition is false.


• Syntax:
• if (condition) {
• // Code if condition is true
• } else {
• // Code if condition is false
• }

Example:

9
#include <stdio.h>

int main() {
int num = 3;
if (num % 2 == 0) {
printf("Even Number\n");
} else {
printf("Odd Number\n");
}
return 0;
}

Output:

Odd Number

Key Points:

• Either the if block or the else block will execute, but not both.
• It provides a clear alternative path in the program flow.

Practice Exercise:

• Write a program to check if a number is divisible by both 2 and 3 using if-else.

3.1.3 if-else if-else Ladder

• Checks multiple conditions sequentially.


• Once a condition is true, the corresponding block executes, and the ladder ends.
• Syntax:
• if (condition1) {
• // Code if condition1 is true
• } else if (condition2) {
• // Code if condition2 is true
• } else {
• // Code if all conditions are false
• }

Example:

#include <stdio.h>

int main() {
int marks = 85;
if (marks >= 90) {
printf("Grade A\n");
} else if (marks >= 75) {
printf("Grade B\n");
} else if (marks >= 50) {
printf("Grade C\n");
} else {
printf("Fail\n");
}
return 0;
}

Output:

Grade B

10
Key Points:

• Conditions are checked top to bottom.


• The first true condition's block is executed, and the rest are ignored.
• else is optional but recommended for handling all other cases.

Practice Exercise:

• Write a program to determine the day of the week based on a number (1 for Sunday, 2 for Monday, etc.).

3.1.4 switch Statement

• Used for multiple constant value comparisons, generally more efficient than multiple if-else statements.
• Syntax:
• switch (expression) {
• case constant1:
• // Code to execute if expression == constant1
• break;
• case constant2:
• // Code to execute if expression == constant2
• break;
• default:
• // Code if no case matches
• }

Example:

#include <stdio.h>

int main() {
char grade = 'B';
switch (grade) {
case 'A':
printf("Excellent!\n");
break;
case 'B':
printf("Good Job!\n");
break;
case 'C':
printf("Average\n");
break;
default:
printf("Invalid Grade\n");
}
return 0;
}

Output:

Good Job!

Key Points:

• break stops the execution of the switch block.


• Without break, execution continues to the next case (fall-through).
• default is optional and executes when no case matches.

Practice Exercise:

11
• Write a calculator program using switch to perform basic arithmetic operations (+, -, *, /).

3.2 Looping Statements (continued)

Loops are used to repeat a block of code multiple times.

3.2.1 for Loop

• Ideal when the number of iterations is known beforehand.


• Syntax:
• for (initialization; condition; increment/decrement) {
• // Code to be executed
• }

Example:

#include <stdio.h>

int main() {
for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}
return 0;
}

Output:

1 2 3 4 5

Explanation:

• Initialization: Executes once at the start (int i = 1).


• Condition: Checked before each iteration (i <= 5).
• Increment/Decrement: Updates the counter (i++).

Practice Exercise:

• Write a program to calculate the factorial of a number using a for loop.

3.2 Looping Statements

Loops allow the repetition of a block of code multiple times based on a condition.

Loops are used to repeat a block of code multiple times.

3.2.2 while Loop

• Pre-test loop – Checks the condition before executing the block.


• Ideal when the number of iterations is not known beforehand.

12
• Syntax:
• while (condition) {
• // Code to be executed
• }

Example:

#include <stdio.h>

int main() {
int i = 1;
while (i <= 5) {
printf("%d ", i);
i++;
}
return 0;
}

Output:

1 2 3 4 5

Explanation:

• Condition (i <= 5) is checked before the block executes.


• If the condition is true, the block executes and then increments i.
• The loop continues until the condition becomes false.

Key Points:

• Use while when the number of iterations depends on a dynamic condition.


• Be careful of infinite loops if the condition never becomes false.

Practice Exercise:

• Write a program to calculate the sum of digits of a number using a while loop.

3.2.3 do-while Loop

• Post-test loop – Executes the block at least once before checking the condition.
• Ideal when the code must run at least once, regardless of the condition.
• Syntax:
• do {
• // Code to be executed
• } while (condition);

Example:

#include <stdio.h>

int main() {
int i = 1;
do {
printf("%d ", i);
i++;
} while (i <= 5);
return 0;
}

13
Output:

1 2 3 4 5

Explanation:

• The block executes first, then the condition (i <= 5) is checked.


• If the condition is true, the loop repeats; otherwise, it ends.

Key Points:

• Guarantees at least one execution, even if the condition is false initially.


• Useful for menu-driven programs where the menu is shown at least once.

Practice Exercise:

• Implement a menu-driven program to perform basic arithmetic operations using a do-while loop.

3.3 Nested Loops and Conditional Statements

• Nested Loops are loops inside other loops.


• Nested Conditional Statements involve if, else, or switch within loops or other conditionals.

Example of Nested Loops:

#include <stdio.h>

int main() {
for (int i = 1; i <= 3; i++) { // Outer Loop
for (int j = 1; j <= 3; j++) { // Inner Loop
printf("%d %d\n", i, j);
}
}
return 0;
}

Output:

1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3

Explanation:

• Outer Loop runs from i = 1 to i = 3.


• Inner Loop runs from j = 1 to j = 3 for each iteration of the outer loop.
• The inner loop completes all iterations before the outer loop increments.

Common Applications:

14
• Printing patterns (triangles, pyramids).
• Matrix operations (addition, multiplication).

Practice Exercise:

• Write a program to print the multiplication table of numbers from 1 to 5 using nested loops.

3.4 Jump Statements

Jump statements alter the normal flow of control in loops and conditional blocks.

3.4.1 break Statement

• Terminates the loop or switch statement and exits the block.


• Often used to exit early if a condition is met.
• Syntax:
• break;

Example:

#include <stdio.h>

int main() {
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // Exit the loop when i equals 5
}
printf("%d ", i);
}
return 0;
}

Output:

1 2 3 4

Key Points:

• Control exits the loop immediately when break is encountered.


• Commonly used in switch statements and loops to stop further iterations.

Practice Exercise:

• Implement a program to search for an element in an array using break to exit once the element is found.

3.4.2 continue Statement

• Skips the current iteration and moves to the next iteration of the loop.
• Syntax:
• continue;

Example:

15
#include <stdio.h>

int main() {
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue; // Skip when i equals 3
}
printf("%d ", i);
}
return 0;
}

Output:

1 2 4 5

Key Points:

• Control jumps to the loop’s increment statement in for loops or the condition check in while loops.
• Often used for filtering conditions within loops.

Practice Exercise:

• Write a program to print all numbers from 1 to 10, except multiples of 3, using continue.

3.4.3 goto Statement

• Jumps to a labeled statement within the same function.


• Use cautiously as it can make code harder to read and maintain.
• Syntax:
• goto label;
• ...
• label:
• // Code to execute

Example:

#include <stdio.h>

int main() {
int i = 1;
start:
printf("%d ", i);
i++;
if (i <= 5) {
goto start;
}
return 0;
}

Output:

1 2 3 4 5

Summary for Module 3:

• Conditional statements control decision-making in programs.

16
• Loops enable repetition of blocks of code.
• Jump statements alter the flow of control.

Module 4: Functions and Recursion


Duration: 1 Week
Objective: Learn how to modularize programs using functions, understand parameter passing techniques,
and master recursion.

Topics Covered:

4.1 Function Definition and Declaration


4.2 Calling Functions and Returning Values
4.3 Function Parameters

• 4.3.1 Call by Value


• 4.3.2 Call by Reference

4.4 Recursion and Recursive Functions


4.5 Storage Classes

• 4.5.1 auto
• 4.5.2 register
• 4.5.3 static
• 4.5.4 extern

4.1 Function Definition and Declaration

• Functions are blocks of code designed to perform specific tasks.


• Function Declaration (Prototype): Tells the compiler about the function’s name, return type, and
parameters before its actual use.
• Function Definition: Contains the actual body of the function.
• Function Call: Executes the function wherever needed in the program.

Syntax:

return_type function_name(parameter_list);

Example:

#include <stdio.h>

// Function Declaration
int add(int, int);

// Main Function
int main() {
int result = add(5, 10); // Function Call
printf("Sum: %d\n", result);
return 0;

17
}

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

Output:

Sum: 15

Explanation:

• int add(int, int); is the function declaration (prototype).


• add(5, 10); is the function call.
• int add(int a, int b) {...} is the function definition.

Key Points:

• Return Type: The data type of the value returned by the function (e.g., int, float, void).
• Function Parameters: Variables used to pass information to the function.
• void as return type means the function does not return any value.

Practice Exercise:

• Write a function to calculate the factorial of a number.

4.2 Calling Functions and Returning Values

• Function Call: Invokes the function using its name and passes the required arguments.
• Return Statement: Sends a value back to the calling function.

Example:

#include <stdio.h>

// Function Declaration
float calculateArea(float radius);

int main() {
float radius = 5.0;
float area = calculateArea(radius); // Function Call
printf("Area: %.2f\n", area);
return 0;
}

// Function Definition
float calculateArea(float radius) {
return 3.14 * radius * radius;
}

Output:

Area: 78.50

Explanation:

18
• float calculateArea(float radius); is the function prototype.
• calculateArea(radius); is the function call.
• return statement sends the calculated area back to the main() function.

Practice Exercise:

• Write a function to find the maximum of three numbers using a return statement.

4.3 Function Parameters

Functions can take input values through parameters. In C, parameters can be passed in two ways:

4.3.1 Call by Value

• Copy of the actual value is passed to the function.


• Changes made to the parameter inside the function do not affect the original value.

Example:

#include <stdio.h>

void increment(int num) {


num++;
printf("Inside Function: %d\n", num);
}

int main() {
int x = 5;
increment(x);
printf("After Function Call: %d\n", x);
return 0;
}

Output:

Inside Function: 6
After Function Call: 5

Explanation:

• Only a copy of x is passed to increment(), so changes inside the function do not affect the original value.

Key Points:

• Safer since the original value is not modified.


• Preferred for basic data types like int, float, char.

Practice Exercise:

• Write a program to swap two numbers using call by value.

19
4.3.2 Call by Reference

• Address of the variable is passed, allowing the function to modify the original value.
• Implemented using pointers in C.

Example:

#include <stdio.h>

void swap(int *a, int *b) {


int temp = *a;
*a = *b;
*b = temp;
}

int main() {
int x = 10, y = 20;
printf("Before Swap: x=%d, y=%d\n", x, y);
swap(&x, &y); // Passing addresses
printf("After Swap: x=%d, y=%d\n", x, y);
return 0;
}

Output:

Before Swap: x=10, y=20


After Swap: x=20, y=10

Explanation:

• int *a, int *b are pointer parameters.


• &x and &y pass the addresses of x and y to the function.
• Changes inside the function affect the original variables.

Key Points:

• Allows modification of the original data.


• Used for large data structures (arrays, structures) to avoid memory overhead.

Practice Exercise:

• Write a function to find the sum of elements in an array using call by reference.

4.4 Recursion and Recursive Functions

• Recursion occurs when a function calls itself to solve a smaller instance of the problem.
• Every recursive function has two parts:
o Base Case: Terminates the recursion.
o Recursive Case: The function calls itself.

Example: Factorial Calculation

#include <stdio.h>

int factorial(int n) {
if (n == 0) { // Base Case
return 1;

20
} else {
return n * factorial(n - 1); // Recursive Call
}
}

int main() {
int num = 5;
printf("Factorial of %d is %d\n", num, factorial(num));
return 0;
}

Output:

Factorial of 5 is 120

Explanation:

• The function keeps calling itself with n-1 until n becomes 0.


• Base case stops recursion and prevents infinite loops.

Key Points:

• Recursive functions can solve problems elegantly, but they consume more memory (stack space).
• Ensure a base case to avoid infinite recursion and stack overflow.

Practice Exercise:

• Write a recursive function to find the Fibonacci series up to a given number.

Summary for Module 4:

• Functions provide modularity and code reusability.


• Call by Value passes a copy, protecting the original data.
• Call by Reference allows modification of the original data using pointers.
• Recursion is powerful but requires a base case to terminate.

Module 5: Arrays and Strings

Duration: 2 Weeks
Objective: Learn how to use arrays to store multiple values, manipulate strings efficiently, and understand
the relationship between arrays and pointers.

Topics Covered:

21
5.1 One-dimensional Arrays
5.2 Multi-dimensional Arrays
5.3 Strings in C
5.4 String Functions

• 5.4.1 strlen()
• 5.4.2 strcpy()
• 5.4.3 strcmp()
• 5.4.4 strcat()

5.5 Pointers and Arrays

5.1 One-dimensional Arrays

• Array: A collection of elements of the same data type stored in contiguous memory locations.
• Fixed Size: The size of the array must be known at compile time.
• Indexing: Array elements are accessed using indices starting from 0.
• Declaration and Initialization:
• data_type array_name[size];

Example:

int numbers[5]; // Declaration


int scores[3] = {85, 90, 95}; // Initialization
char vowels[5] = {'a', 'e', 'i', 'o', 'u'}; // Character Array

Example:

#include <stdio.h>

int main() {
int numbers[5] = {10, 20, 30, 40, 50};

// Accessing and Printing Array Elements


for (int i = 0; i < 5; i++) {
printf("Element %d: %d\n", i, numbers[i]);
}

return 0;
}

Output:

Element 0: 10
Element 1: 20
Element 2: 30
Element 3: 40
Element 4: 50

Key Points:

• Array indices start from 0 and go up to size-1.


• Accessing an out-of-bound index leads to undefined behavior.
• Arrays in C are stored in contiguous memory locations.

Practice Exercise:

22
• Write a program to find the maximum and minimum elements in an array.

5.2 Multi-dimensional Arrays

• Multi-dimensional Arrays are arrays of arrays, mainly used for matrices and tables.
• 2D Arrays: Used for storing data in rows and columns (e.g., matrices).
• Declaration and Initialization:
• data_type array_name[rows][columns];

Example:

int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

Example:

#include <stdio.h>

int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

// Printing the 2D Array


for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}

return 0;
}

Output:

1 2 3
4 5 6

Key Points:

• Rows and columns are indexed starting from 0.


• Memory is stored in row-major order in C.
• Elements are accessed using two indices: matrix[row][column].

Practice Exercise:

• Write a program to add two matrices using 2D arrays.

5.3 Strings in C

• String: A sequence of characters terminated by a null character (\0).

23
• In C, strings are implemented as character arrays.
• Declaration and Initialization:
• char str[10] = "Hello";
• char name[] = {'J', 'o', 'h', 'n', '\0'};
• %s is used in printf and scanf for strings.

Example:

#include <stdio.h>

int main() {
char name[20];
printf("Enter your name: ");
scanf("%s", name); // No '&' required for strings
printf("Hello, %s!\n", name);
return 0;
}

Input:

Enter your name: Alice

Output:

Hello, Alice!

Key Points:

• Strings end with a null character (\0).


• scanf() stops reading input at whitespace.
• Use gets() and puts() for reading and writing strings with spaces (deprecated).
• Prefer fgets() for safer input with spaces.

Practice Exercise:

• Write a program to reverse a string entered by the user.

5.4 String Functions

C provides several built-in functions in the <string.h> library for manipulating strings.

5.4.1 strlen()

• Returns the length of a string (excluding the null character).


• Syntax:
• int strlen(const char *str);

Example:

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

int main() {
char text[] = "Hello, World!";

24
printf("Length of string: %d\n", strlen(text));
return 0;
}

Output:

Length of string: 13

5.4.2 strcpy()

• Copies one string into another.


• Syntax:
• char *strcpy(char *destination, const char *source);

Example:

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

int main() {
char src[] = "Hello";
char dest[10];
strcpy(dest, src);
printf("Copied String: %s\n", dest);
return 0;
}

Output:

Copied String: Hello

5.4.3 strcmp()

• Compares two strings lexicographically.


• Returns:
o 0 if equal
o < 0 if first string is less
o > 0 if first string is greater
• Syntax:
• int strcmp(const char *str1, const char *str2);

Example:

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

int main() {
char str1[] = "apple";
char str2[] = "banana";
int result = strcmp(str1, str2);
if (result < 0) {
printf("str1 is less than str2\n");
} else if (result > 0) {
printf("str1 is greater than str2\n");
} else {
printf("str1 is equal to str2\n");
}
return 0;
}

25
Output:

str1 is less than str2

5.4.4 strcat()

• Concatenates (joins) two strings.


• Syntax:
• char *strcat(char *destination, const char *source);

Example:

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

int main() {
char str1[20] = "Hello, ";
char str2[] = "World!";
strcat(str1, str2);
printf("Concatenated String: %s\n", str1);
return 0;
}

Output:

Concatenated String: Hello, World!

5.5 Pointers and Arrays

• In C, arrays and pointers are closely related.


• An array name is a constant pointer to the first element of the array.
• Pointer arithmetic can be used to traverse arrays efficiently.
• Example: If arr is an array, then arr is equivalent to &arr[0].

5.5.1 Array and Pointer Relationship

• Array Name as Pointer: The name of an array is a constant pointer to its first element.
• Accessing array elements using pointers:
o arr[i] is equivalent to *(arr + i)
o &arr[i] is equivalent to (arr + i)

Example:

#include <stdio.h>

int main() {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // Pointer to the first element

// Accessing array elements using pointer


for (int i = 0; i < 5; i++) {
printf("Element %d: %d\n", i, *(ptr + i));
}

return 0;

26
}

Output:

Element 0: 10
Element 1: 20
Element 2: 30
Element 3: 40
Element 4: 50

Explanation:

• int *ptr = arr; assigns the pointer to the first element of the array.
• *(ptr + i) accesses the array elements using pointer arithmetic.
• arr[i] is equivalent to *(arr + i).

Key Points:

• arr points to the first element (&arr[0]).


• arr + i points to the i-th element.
• *(arr + i) accesses the value of the i-th element.

Practice Exercise:

• Write a program to calculate the sum of elements in an array using pointers.

5.5.2 Passing Arrays to Functions

• Arrays are always passed to functions as pointers.


• The called function receives the address of the first element, not a copy of the array.
• Syntax:
• void function_name(data_type *array, int size);

Example:

#include <stdio.h>

// Function to print array elements


void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}

int main() {
int numbers[] = {1, 2, 3, 4, 5};
printArray(numbers, 5); // Passing array to function
return 0;
}

Output:

1 2 3 4 5

Explanation:

• printArray(int *arr, int size) takes a pointer to the array and its size as arguments.

27
• The array is accessed inside the function using pointer notation *(arr + i).
• Changes made to the array elements inside the function affect the original array.

Key Points:

• Arrays are passed by reference (using pointers), so changes inside the function persist outside.
• Only the base address of the array is passed, making it memory efficient.

Practice Exercise:

• Write a function to reverse an array using pointers.

Summary for Module 5:

• Arrays are fixed-size collections of elements stored in contiguous memory locations.


• Multi-dimensional arrays are useful for matrices and tables.
• Strings are character arrays ending with a null character (\0).
• C provides built-in string functions for manipulation (strlen, strcpy, strcmp, strcat).
• Arrays and pointers are closely related, enabling powerful and efficient memory manipulation.

Module 6: Pointers and Dynamic Memory Allocation

Duration: 2 Weeks
Objective: Master the use of pointers, pointer arithmetic, and dynamic memory allocation for efficient
memory management.

Topics Covered:

6.1 Pointer Basics and Pointer Arithmetic


6.2 Pointers and Arrays
6.3 Pointers to Functions
6.4 Pointers to Structures
6.5 Dynamic Memory Allocation

• 6.5.1 malloc()
• 6.5.2 calloc()
• 6.5.3 realloc()
• 6.5.4 free()

6.1 Pointer Basics and Pointer Arithmetic

• Pointer: A variable that stores the memory address of another variable.


• Declaration and Initialization:
• data_type *pointer_name;

28
• pointer_name = &variable;

Example:

int num = 10;


int *ptr = &num;

Example:

#include <stdio.h>

int main() {
int num = 10;
int *ptr = &num;

printf("Value of num: %d\n", num);


printf("Address of num: %p\n", &num);
printf("Pointer pointing to address: %p\n", ptr);
printf("Value at the address: %d\n", *ptr);

return 0;
}

Output:

Value of num: 10
Address of num: (some memory address)
Pointer pointing to address: (same memory address)
Value at the address: 10

Explanation:

• int *ptr = &num; initializes ptr to point to the address of num.


• *ptr dereferences the pointer to access the value at that address.
• Pointer Arithmetic:
o Incrementing (ptr++) moves to the next memory location.
o Decrementing (ptr--) moves to the previous memory location.

Example of Pointer Arithmetic:

#include <stdio.h>

int main() {
int arr[] = {10, 20, 30};
int *ptr = arr;

printf("Current value: %d\n", *ptr); // 10


ptr++;
printf("Next value: %d\n", *ptr); // 20

return 0;
}

Key Points:

• Pointers can be incremented or decremented to navigate arrays.


• Incrementing a pointer moves to the next element of the data type it points to.
• Pointers are essential for dynamic memory allocation and function arguments.

Practice Exercise:

29
• Write a program to calculate the sum of an array using pointers and pointer arithmetic.

6.2 Pointers and Arrays

• Arrays and pointers are closely related in C.


• An array name is a constant pointer to the first element of the array.
• Pointers can be used to traverse arrays using pointer arithmetic.
• Example: If arr is an array, then arr is equivalent to &arr[0].

Example:

#include <stdio.h>

int main() {
int numbers[] = {10, 20, 30, 40, 50};
int *ptr = numbers; // Pointer to the first element

// Accessing array elements using pointer


for (int i = 0; i < 5; i++) {
printf("Element %d: %d\n", i, *(ptr + i));
}

return 0;
}

Output:

Element 0: 10
Element 1: 20
Element 2: 30
Element 3: 40
Element 4: 50

Explanation:

• int *ptr = numbers; initializes the pointer to the first element of the array.
• *(ptr + i) accesses array elements using pointer arithmetic.
• numbers[i] is equivalent to *(numbers + i).

Key Points:

• numbers is a constant pointer to the first element (&numbers[0]).


• numbers + i points to the i-th element.
• *(numbers + i) accesses the value of the i-th element.

Practice Exercise:

• Write a program to reverse an array using pointers.

6.2.1 Passing Arrays to Functions using Pointers

• Arrays are always passed to functions as pointers.


• The called function receives the address of the first element, not a copy of the array.
• Syntax:
• void function_name(data_type *array, int size);

30
Example:

#include <stdio.h>

// Function to print array elements


void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}

int main() {
int numbers[] = {1, 2, 3, 4, 5};
printArray(numbers, 5); // Passing array to function
return 0;
}

Output:

1 2 3 4 5

Explanation:

• printArray(int *arr, int size) takes a pointer to the array and its size as arguments.
• The array is accessed inside the function using pointer notation *(arr + i).
• Changes made to the array elements inside the function affect the original array.

Key Points:

• Arrays are passed by reference (using pointers), so changes inside the function persist outside.
• Only the base address of the array is passed, making it memory efficient.

Practice Exercise:

• Write a function to find the largest element in an array using pointers.

6.3 Pointers to Functions

• In C, function pointers can be used to store the address of a function.


• They allow dynamic function calls, useful for callbacks and implementing function tables.
• Declaration and Initialization:
• return_type (*pointer_name)(parameter_list) = function_name;

Example:

#include <stdio.h>

// Functions
int add(int a, int b) {
return a + b;
}

int multiply(int a, int b) {


return a * b;
}

int main() {

31
// Declaring Function Pointers
int (*funcPtr)(int, int);

// Pointing to add() function


funcPtr = add;
printf("Sum: %d\n", funcPtr(10, 5));

// Pointing to multiply() function


funcPtr = multiply;
printf("Product: %d\n", funcPtr(10, 5));

return 0;
}

Output:

Sum: 15
Product: 50

Explanation:

• int (*funcPtr)(int, int); declares a pointer to a function that takes two int arguments and returns
an int.
• funcPtr = add; assigns the address of add() to the pointer.
• funcPtr(10, 5); calls the function through the pointer.

Key Points:

• Function pointers are type-safe, meaning the signature must match exactly.
• They are useful for implementing callback functions and dynamic dispatch.

Practice Exercise:

• Implement a menu-driven calculator using function pointers.

6.4 Pointers to Structures

• Pointers to structures allow efficient manipulation of structures, especially in functions.


• Accessing members using pointers is done using the arrow operator (->).
• Declaration and Initialization:
• struct StructureName *pointer_name;
• pointer_name = &variable_name;

Example:

#include <stdio.h>

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

int main() {
// Initializing a Structure
struct Student s1 = {"Alice", 20, 3.8};

32
// Pointer to Structure
struct Student *ptr = &s1;

// Accessing members using pointer


printf("Name: %s\n", ptr->name);
printf("Age: %d\n", ptr->age);
printf("GPA: %.2f\n", ptr->gpa);

return 0;
}

Output:

Name: Alice
Age: 20
GPA: 3.80

Explanation:

• struct Student *ptr = &s1; declares a pointer to the structure s1.


• ptr->name accesses the name member using the arrow operator (->).
• Using ptr-> is equivalent to (*ptr).member.

Key Points:

• Arrow Operator (->) is used to access members through pointers.


• Pointers to structures are useful for passing large structures to functions efficiently.

Practice Exercise:

• Write a program to input and display details of multiple students using an array of structure pointers.

6.5 Dynamic Memory Allocation

• Dynamic Memory Allocation allows programs to request memory at runtime, as opposed to compile-time
(static memory).
• It is useful when the size of data structures (arrays, linked lists) is unknown during compilation.
• Allocated memory resides in the heap area and must be manually managed (allocated and freed).
• Header Required: <stdlib.h>

6.5.1 malloc() – Memory Allocation

• malloc() (Memory Allocation) dynamically allocates a block of memory.


• Uninitialized Memory: The allocated memory contains garbage values.
• Returns a pointer to the beginning of the block, or NULL if allocation fails.
• Syntax:
• pointer = (data_type*) malloc(size_in_bytes);

Example:

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

int main() {
int *arr;

33
int n;

printf("Enter number of elements: ");


scanf("%d", &n);

// Dynamic memory allocation using malloc()


arr = (int*) malloc(n * sizeof(int));

if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

// Input and Output


for (int i = 0; i < n; i++) {
printf("Enter element %d: ", i + 1);
scanf("%d", &arr[i]);
}

printf("Array Elements: ");


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

// Freeing the allocated memory


free(arr);

return 0;
}

Output:

Enter number of elements: 3


Enter element 1: 10
Enter element 2: 20
Enter element 3: 30
Array Elements: 10 20 30

Explanation:

• (int*) is used to typecast the returned pointer to the desired data type.
• malloc(n * sizeof(int)) allocates memory for n integers.
• free(arr); releases the allocated memory.

Key Points:

• If malloc() fails, it returns NULL. Always check for NULL before using the pointer.
• Memory allocated using malloc() is not initialized, so it contains garbage values.

Practice Exercise:

• Write a program to find the sum of n numbers using malloc() for dynamic memory allocation.

6.5.2 calloc() – Contiguous Allocation

• calloc() (Contiguous Allocation) dynamically allocates memory for an array of elements.


• Initialized Memory: Initializes all elements to 0.
• Returns a pointer to the first byte of the allocated memory, or NULL if allocation fails.
• Syntax:

34
• pointer = (data_type*) calloc(num_elements, size_of_each_element);

Example:

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

int main() {
int *arr;
int n;

printf("Enter number of elements: ");


scanf("%d", &n);

// Dynamic memory allocation using calloc()


arr = (int*) calloc(n, sizeof(int));

if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

// Output (all elements initialized to 0)


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

// Freeing the allocated memory


free(arr);

return 0;
}

Output:

Enter number of elements: 4


Array Elements: 0 0 0 0

Explanation:

• calloc(n, sizeof(int)) allocates memory for n integers initialized to 0.


• It takes two arguments:
o num_elements: Number of elements.
o size_of_each_element: Size of each element in bytes.

Key Points:

• calloc() initializes the allocated memory to zero.


• It is useful when you need to ensure all values are initialized.

Practice Exercise:

• Write a program to input n strings using calloc() and display them.

6.5.3 realloc() – Reallocate Memory

• realloc() resizes previously allocated memory using malloc() or calloc() without losing the old data.

35
• If the new size is larger, the additional memory remains uninitialized.
• If the new size is smaller, the excess memory is released.
• Syntax:
• pointer = (data_type*) realloc(pointer, new_size_in_bytes);

Example:

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

int main() {
int *arr;
int n = 3;

// Initial Allocation
arr = (int*) malloc(n * sizeof(int));
printf("Enter 3 elements:\n");
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}

// Reallocate memory for 2 more elements


n = 5;
arr = (int*) realloc(arr, n * sizeof(int));
printf("Enter 2 more elements:\n");
for (int i = 3; i < n; i++) {
scanf("%d", &arr[i]);
}

// Displaying all elements


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

// Freeing the allocated memory


free(arr);

return 0;
}

Output:

Enter 3 elements:
10 20 30
Enter 2 more elements:
40 50
Array Elements: 10 20 30 40 50

Explanation:

• realloc() adjusts the memory size while preserving existing data.


• If realloc() fails, the original memory remains unchanged.

Key Points:

• If the pointer passed to realloc() is NULL, it behaves like malloc().


• If the new size is 0, it behaves like free() and returns NULL.

Practice Exercise:

36
• Implement a dynamic list that grows as the user inputs more elements using realloc().

6.5.4 free() – Deallocation of Memory

• free() is used to release dynamically allocated memory back to the system.


• This prevents memory leaks by ensuring that memory no longer needed is freed.
• Syntax:
• free(pointer);

Example:

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

int main() {
int *arr;
int n;

printf("Enter number of elements: ");


scanf("%d", &n);

// Dynamic memory allocation using malloc()


arr = (int*) malloc(n * sizeof(int));

if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

// Input and Output


for (int i = 0; i < n; i++) {
printf("Enter element %d: ", i + 1);
scanf("%d", &arr[i]);
}

printf("Array Elements: ");


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

// Freeing the allocated memory


free(arr);

return 0;
}

Output:

Enter number of elements: 3


Enter element 1: 10
Enter element 2: 20
Enter element 3: 30
Array Elements: 10 20 30

Explanation:

• free(arr); releases the memory allocated by malloc() back to the system.


• After calling free(), the pointer becomes a dangling pointer (points to a freed memory location).
• It is good practice to set the pointer to NULL after freeing memory to avoid accidental access.

37
Key Points:

• free() only releases memory allocated dynamically using malloc(), calloc(), or realloc().
• Never use free() on memory not allocated dynamically, as it causes undefined behavior.
• Always set the pointer to NULL after freeing to avoid dangling pointers.

Example of Avoiding Dangling Pointer:

free(arr);
arr = NULL; // Pointer now points to nothing

Practice Exercise:

• Write a program to dynamically allocate memory for n integers, input values, calculate the sum, and
free the memory.

Summary for Module 6:

• Dynamic Memory Allocation provides flexibility to allocate and free memory at runtime.
• malloc() allocates uninitialized memory, while calloc() initializes it to zero.
• realloc() resizes the previously allocated memory without losing old data.
• free() releases allocated memory, preventing memory leaks.
• Always set pointers to NULL after freeing to avoid dangling pointers.

Module 7: Structures and Unions


Duration: 1 Week
Objective: Learn how to organize and manage complex data using structures and unions in C.

Topics Covered:

7.1 Defining and Using Structures


7.2 Nested Structures
7.3 Array of Structures
7.4 Unions and Memory Sharing
7.5 typedef and enum
7.6 Bit Fields and Memory Packing

7.1 Defining and Using Structures

• Structure: A user-defined data type that groups different data types under one name.
• Structures are used to represent a record or complex data entity (e.g., student details, employee
records).
• Declaration and Definition:
• struct StructureName {
• data_type member1;

38
• data_type member2;
• ...
• };
• Example:
• struct Student {
• char name[50];
• int age;
• float gpa;
• };

Example of Using Structure:

#include <stdio.h>

// Defining Structure
struct Student {
char name[50];
int age;
float gpa;
};

int main() {
// Declaring and Initializing Structure Variable
struct Student s1 = {"Alice", 20, 3.8};

// Accessing Members
printf("Name: %s\n", s1.name);
printf("Age: %d\n", s1.age);
printf("GPA: %.2f\n", s1.gpa);

return 0;
}

Output:

Name: Alice
Age: 20
GPA: 3.80

Explanation:

• struct Student is a user-defined data type that groups name, age, and gpa.
• s1 is a structure variable of type struct Student.
• Dot operator (.) is used to access the members of the structure.

Key Points:

• Structures group different data types under one name.


• Structure members are stored in contiguous memory locations.
• Use the dot operator (.) to access members.

Practice Exercise:

• Create a structure for employee details (name, ID, salary) and input/display details of multiple
employees.

7.2 Nested Structures

39
• Nested Structures are structures within other structures.
• Useful for representing hierarchical data like address details within student records.
• Example:
• struct Address {
• char city[50];
• char state[50];
• };

• struct Student {
• char name[50];
• struct Address addr;
• };

Example of Nested Structures:

#include <stdio.h>

// Defining Nested Structures


struct Address {
char city[50];
char state[50];
};

struct Student {
char name[50];
int age;
struct Address addr;
};

int main() {
// Initializing Nested Structure
struct Student s1 = {"Bob", 21, {"New York", "NY"}};

// Accessing Nested Members


printf("Name: %s\n", s1.name);
printf("Age: %d\n", s1.age);
printf("City: %s\n", s1.addr.city);
printf("State: %s\n", s1.addr.state);

return 0;
}

Output:

Name: Bob
Age: 21
City: New York
State: NY

Explanation:

• struct Address is defined within struct Student.


• Nested members are accessed using dot notation (s1.addr.city).

Key Points:

• Nested structures enhance organization and readability.


• Use the dot operator (.) recursively to access nested members.

Practice Exercise:

40
• Create a nested structure for a book's details (title, author, and publication address).

7.3 Array of Structures

• An Array of Structures is used to store multiple records of the same type.


• Useful for managing collections of data, such as a list of students or employee records.
• Declaration and Initialization:
• struct StructureName array_name[size];

Example:

struct Student students[10];

Example of Array of Structures:

#include <stdio.h>

// Defining Structure
struct Student {
char name[50];
int age;
float gpa;
};

int main() {
// Declaring Array of Structures
struct Student students[3];

// Input for Multiple Students


for (int i = 0; i < 3; i++) {
printf("Enter details for Student %d:\n", i + 1);
printf("Name: ");
scanf("%s", students[i].name);
printf("Age: ");
scanf("%d", &students[i].age);
printf("GPA: ");
scanf("%f", &students[i].gpa);
}

// Displaying Student Details


printf("\nStudent Details:\n");
for (int i = 0; i < 3; i++) {
printf("Name: %s, Age: %d, GPA: %.2f\n", students[i].name, students[i].age,
students[i].gpa);
}

return 0;
}

Output:

Enter details for Student 1:


Name: Alice
Age: 20
GPA: 3.8
Enter details for Student 2:
Name: Bob
Age: 21
GPA: 3.5

41
Enter details for Student 3:
Name: Charlie
Age: 22
GPA: 3.9

Student Details:
Name: Alice, Age: 20, GPA: 3.80
Name: Bob, Age: 21, GPA: 3.50
Name: Charlie, Age: 22, GPA: 3.90

Explanation:

• struct Student students[3]; declares an array of 3 structures.


• students[i].name, students[i].age, and students[i].gpa are used to access members for
each student.
• The loop iterates through the array to input and display the details.

Key Points:

• Array of structures allows you to group and manage multiple records efficiently.
• Members are accessed using the dot operator (.) and array indexing.
• Each element of the array is a structure variable.

Practice Exercise:

• Create an array of structures for storing book details (title, author, price) and display the information.

7.4 Unions and Memory Sharing

• A Union is similar to a structure but shares memory among its members.


• Only one member can hold a value at a time.
• The size of a union is equal to the size of its largest member, as all members share the same
memory location.
• Declaration and Definition:
• union UnionName {
• data_type member1;
• data_type member2;
• ...
• };

Example:

union Data {
int num;
float fnum;
char ch;
};

Example of Union:

#include <stdio.h>

// Defining Union
union Data {
int num;
float fnum;
char ch;

42
};

int main() {
// Declaring Union Variable
union Data data;

// Assigning and Displaying Integer Value


data.num = 10;
printf("num: %d\n", data.num);

// Assigning and Displaying Float Value


data.fnum = 98.76;
printf("fnum: %.2f\n", data.fnum);

// Assigning and Displaying Character Value


data.ch = 'A';
printf("ch: %c\n", data.ch);

return 0;
}

Output:

num: 10
fnum: 98.76
ch: A

Explanation:

• union Data groups num, fnum, and ch into one memory location.
• Memory Sharing: Only one member can store a value at a time, as they share the same memory
space.
• Last assigned value is retained, while the previous values are overwritten.

Key Points:

• Memory Efficient: Saves memory by sharing space among members.


• Only one member is active at a time.
• Changing the value of one member affects all other members.

Example of Memory Sharing:

#include <stdio.h>

union Data {
int num;
char ch;
};

int main() {
union Data data;
data.num = 65;
printf("num: %d\n", data.num);
printf("ch: %c\n", data.ch); // 'A' (ASCII value 65)
return 0;
}

Output:

num: 65
ch: A

43
Explanation:

• The same memory location is used for both num and ch.
• Since num is 65 (ASCII value of A), ch also displays A.

Key Points:

• Size of Union is equal to the size of its largest member.


• All members share the same memory location, so updating one member affects others.

Practice Exercise:

• Create a union to store different data types (int, float, char) and display their values to observe
memory sharing.

7.5 typedef and enum

• typedef: Used to create custom data type names for existing data types.
• enum: Used to define named integer constants for better code readability and maintainability.

7.5.1 typedef – Custom Data Types

• typedef creates aliases for existing data types, improving code readability and portability.
• It is commonly used for structures, pointers, and complex data types.
• Syntax:
• typedef existing_data_type new_name;

Example:

typedef unsigned long int ULI;


ULI bigNumber;

Example:

#include <stdio.h>

// Creating Alias for Structure


typedef struct {
char name[50];
int age;
float gpa;
} Student;

int main() {
// Using Custom Data Type Name
Student s1 = {"Alice", 20, 3.8};

// Accessing Members
printf("Name: %s\n", s1.name);
printf("Age: %d\n", s1.age);
printf("GPA: %.2f\n", s1.gpa);

return 0;
}

44
Output:

Name: Alice
Age: 20
GPA: 3.80

Explanation:

• typedef struct { ... } Student; creates an alias Student for the structure type.
• Student s1 is used instead of struct Student s1, making the code cleaner and more readable.
• typedef improves code maintainability and portability.

Key Points:

• typedef does not create new data types; it just provides a new name (alias).
• Improves readability and makes complex data types easier to use.
• Commonly used with structures, pointers, and arrays.

Practice Exercise:

• Use typedef to create an alias for a pointer to an integer and demonstrate pointer manipulation.

7.5.2 enum – Enumerated Constants

• enum defines named integer constants, enhancing code readability and maintainability.
• By default, the first value is 0, and each subsequent value is incremented by 1.
• Syntax:
• enum EnumName {
• constant1,
• constant2,
• ...
• };

Example:

enum Weekday { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY };

Example:

#include <stdio.h>

// Defining Enumerated Type


enum Direction {
NORTH,
EAST,
SOUTH,
WEST
};

int main() {
enum Direction dir;

dir = EAST;
printf("Direction: %d\n", dir);

dir = SOUTH;
printf("Direction: %d\n", dir);

45
return 0;
}

Output:

Direction: 1
Direction: 2

Explanation:

• enum Direction defines constants NORTH, EAST, SOUTH, WEST with values 0, 1, 2, and 3 respectively.
• enum Direction dir declares a variable of type enum Direction.
• Enum constants are integers and can be used in control structures (if, switch).

Custom Values in enum:

• You can assign specific values to constants.


• enum Days {
• MON = 1,
• TUE = 2,
• WED = 3,
• THU = 4,
• FRI = 5,
• SAT = 6,
• SUN = 7
• };

Key Points:

• Named constants improve readability and maintainability.


• Enum constants are integers and can be compared or used in arithmetic operations.
• Enum variables can only hold values defined in the enum.

Practice Exercise:

• Define an enum for the days of the week and use it in a switch statement to display the name of the day.

Summary for Module 7:

• Structures group different data types under one name and are useful for organizing complex data.
• Nested Structures allow hierarchical data representation.
• Array of Structures manages multiple records efficiently.
• Unions share memory among members, saving space but allowing only one active member at a time.
• Unions are memory efficient but require careful usage to avoid unintentional data overwrites.

• typedef creates aliases for existing data types, improving readability.

• enum defines named integer constants, making the code easier to understand and maintain.
• Both are essential tools for writing cleaner and more maintainable code.

Module 8: File Handling

46
Duration: 1 Week
Objective: Learn how to perform input and output operations with files for data storage and retrieval.

Topics Covered:

8.1 File Operations

• 8.1.1 fopen() and fclose()


• 8.1.2 fprintf() and fscanf()
• 8.1.3 fgets() and fputs()

8.2 File Modes

• 8.2.1 r, w, a, r+, w+, a+

8.3 Reading and Writing Files


8.4 Random Access in Files

• 8.4.1 fseek()
• 8.4.2 ftell()
• 8.4.3 rewind()

8.5 Command Line Arguments

8.1 File Operations

• File Handling is used to store data permanently on a disk.


• C provides several functions for file operations in the <stdio.h> library.
• Steps in File Handling:
1. Open the file using fopen()
2. Perform read/write operations using functions like fprintf(), fscanf(), fgets(), fputs()
3. Close the file using fclose()

8.1.1 fopen() and fclose()

• fopen() is used to open a file in a specific mode (read, write, append).


• It returns a file pointer of type FILE*.
• fclose() closes an opened file and releases resources.
• Syntax:
• FILE *fp;
• fp = fopen("filename", "mode");
• fclose(fp);

Example:

FILE *fp;
fp = fopen("data.txt", "r");
if (fp == NULL) {
printf("File not found!\n");
} else {
printf("File opened successfully.\n");

47
fclose(fp);
}

8.1.2 fprintf() and fscanf()

• fprintf() is used to write formatted data to a file, similar to printf() for the console.
• fscanf() is used to read formatted data from a file, similar to scanf() for standard input.
• Both functions require a file pointer and a format specifier.

fprintf() – Writing to a File

• fprintf() writes formatted data to a file.


• Syntax:
• fprintf(file_pointer, "format_string", variables);

Example:

FILE *fp;
fp = fopen("data.txt", "w");
fprintf(fp, "Hello, World!\n");
fclose(fp);

Example:

#include <stdio.h>

int main() {
FILE *fp;
char name[50];
int age;
float gpa;

// Input from User


printf("Enter Name: ");
scanf("%s", name);
printf("Enter Age: ");
scanf("%d", &age);
printf("Enter GPA: ");
scanf("%f", &gpa);

// Writing to File using fprintf()


fp = fopen("student.txt", "w");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

fprintf(fp, "Name: %s\nAge: %d\nGPA: %.2f\n", name, age, gpa);


fclose(fp);

printf("Data successfully written to student.txt\n");

return 0;
}

Output:

Enter Name: Alice


Enter Age: 20

48
Enter GPA: 3.8
Data successfully written to student.txt

Explanation:

• fprintf() writes formatted data to the file student.txt.


• w mode creates the file if it does not exist or overwrites the file if it already exists.
• Always close the file using fclose() after writing.

Key Points:

• fprintf() is used for formatted output to a file.


• It supports all format specifiers (%d, %f, %s, etc.) used in printf().
• In w mode, existing content is overwritten.

Practice Exercise:

• Write a program to input details of multiple books (title, author, price) and store them in a file using
fprintf().

fscanf() – Reading from a File

• fscanf() reads formatted data from a file, similar to scanf() for standard input.
• Syntax:
• fscanf(file_pointer, "format_string", &variables);

Example:

FILE *fp;
fp = fopen("data.txt", "r");
fscanf(fp, "%s %d %f", name, &age, &gpa);
fclose(fp);

Example:

#include <stdio.h>

int main() {
FILE *fp;
char name[50];
int age;
float gpa;

// Reading from File using fscanf()


fp = fopen("student.txt", "r");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

fscanf(fp, "Name: %s\nAge: %d\nGPA: %f\n", name, &age, &gpa);


fclose(fp);

// Displaying the Data


printf("Student Details:\n");
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("GPA: %.2f\n", gpa);

49
return 0;
}

Output (from previously written file):

Student Details:
Name: Alice
Age: 20
GPA: 3.80

Explanation:

• fscanf() reads formatted data from the file student.txt.


• Format specifiers must match the format used during writing.
• Always close the file using fclose() after reading.

Key Points:

• fscanf() is used for formatted input from a file.


• It supports the same format specifiers as scanf().
• Reading stops at whitespace, so avoid spaces in strings or use fgets() for strings with spaces.

Practice Exercise:

• Write a program to read and display the details of multiple books stored in a file.

8.2 File Modes

File modes specify the purpose of opening a file (e.g., read, write, append). They determine the file's
behavior, such as creation, overwriting, or updating.

8.2.1 r – Read Mode

• Opens an existing file for reading.


• If the file does not exist, fopen() returns NULL.
• Syntax:
• FILE *fp = fopen("filename.txt", "r");

8.2.2 w – Write Mode

• Opens a file for writing.


• If the file does not exist, it is created.
• If the file exists, previous content is overwritten.
• Syntax:
• FILE *fp = fopen("filename.txt", "w");

8.2.3 a – Append Mode

• Opens a file for appending data.


• If the file does not exist, it is created.
• Existing data is not overwritten; new data is added at the end.

50
• Syntax:
• FILE *fp = fopen("filename.txt", "a");

8.2.4 r+ – Read and Update

• Opens an existing file for reading and updating.


• If the file does not exist, fopen() returns NULL.
• Syntax:
• FILE *fp = fopen("filename.txt", "r+");

8.2.5 w+ – Write and Update

• Opens a file for reading and writing.


• If the file exists, its contents are erased.
• If the file does not exist, it is created.
• Syntax:
• FILE *fp = fopen("filename.txt", "w+");

Example:

#include <stdio.h>

int main() {
FILE *fp;
char text[100];

// Opening file in w+ mode (write and read)


fp = fopen("sample.txt", "w+");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

// Writing to File
fprintf(fp, "Hello, World!\n");

// Moving file pointer to the beginning


rewind(fp);

// Reading from File


fgets(text, 100, fp);
printf("File Content: %s", text);

fclose(fp);

return 0;
}

Output:

File Content: Hello, World!

Explanation:

• w+ mode creates the file if it does not exist or erases the content if it exists.
• fprintf() writes to the file, and rewind(fp); resets the file pointer to the beginning.
• fgets() reads the file content back into the program.

Key Points:

51
• w+ is used for both reading and writing.
• Existing content is erased when the file is opened.
• The file pointer is at the beginning of the file after opening.

Practice Exercise:

• Write a program that writes multiple lines to a file in w+ mode and then reads them back.

8.2.6 a+ – Append and Update

• Opens a file for reading and appending.


• If the file exists, new data is appended at the end.
• If the file does not exist, it is created.
• Syntax:
• FILE *fp = fopen("filename.txt", "a+");

Example:

#include <stdio.h>

int main() {
FILE *fp;
char text[100];

// Opening file in a+ mode (append and read)


fp = fopen("sample.txt", "a+");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

// Appending to File
fprintf(fp, "Appending this line.\n");

// Moving file pointer to the beginning


rewind(fp);

// Reading from File


printf("File Content:\n");
while (fgets(text, 100, fp) != NULL) {
printf("%s", text);
}

fclose(fp);

return 0;
}

Output (if file already contains some text):

File Content:
Hello, World!
Appending this line.

Explanation:

• a+ mode opens the file for reading and appending.


• Existing content is preserved, and new data is appended at the end.
• rewind(fp); resets the pointer to the beginning for reading.

52
Key Points:

• a+ is used for reading and appending.


• New content is appended at the end without erasing existing data.
• The file pointer is at the end of the file after opening.

Practice Exercise:

• Write a program to log user inputs to a file using a+ mode, preserving previous entries.

Summary of File Modes:

Mode Description File Pointer Position


r Read-only Beginning
w Write-only (Overwrites or Creates) Beginning
a Append-only (Creates if not exists) End
r+ Read and Update Beginning
w+ Write and Update (Overwrites or Creates) Beginning
a+ Append and Update (Creates if not exists) End

8.3 Reading and Writing Files

• C provides several functions to read from and write to files.


• These functions include:
o fprintf() and fscanf() – Formatted input and output.
o fgets() and fputs() – String input and output.
o fread() and fwrite() – Binary input and output.

8.3.1 fgets() and fputs() – String Input and Output

• fgets() reads a line from a file into a string.


• fputs() writes a string to a file.

Syntax:

fgets(string, size, file_pointer);


fputs(string, file_pointer);

Example:

#include <stdio.h>

int main() {
FILE *fp;
char line[100];

// Writing to File using fputs()


fp = fopen("notes.txt", "w");
fputs("This is a line of text.\n", fp);
fputs("This is another line.\n", fp);
fclose(fp);

// Reading from File using fgets()

53
fp = fopen("notes.txt", "r");
printf("File Content:\n");
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
fclose(fp);

return 0;
}

Output:

File Content:
This is a line of text.
This is another line.

Explanation:

• fputs() writes strings to the file notes.txt.


• fgets() reads the file line by line until the end of the file (NULL).

Key Points:

• fgets() stops reading at a newline or the specified size.


• fputs() does not add a newline automatically.

Practice Exercise:

• Write a program to read and display a file's contents line by line using fgets().

8.3.2 fread() and fwrite() – Binary Input and Output

• fread() reads binary data from a file into a block of memory.


• fwrite() writes binary data from a block of memory to a file.
• These functions are commonly used for reading and writing binary files (e.g., images, executables).

fwrite() – Writing Binary Data

• fwrite() writes a block of data from memory to a binary file.


• Syntax:
• size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
o ptr: Pointer to the data to be written.
o size: Size of each element.
o count: Number of elements to write.
o stream: File pointer.

Example:

#include <stdio.h>

struct Student {
char name[50];
int age;
float gpa;
};

54
int main() {
FILE *fp;
struct Student s1 = {"Alice", 20, 3.8};

// Writing Binary Data using fwrite()


fp = fopen("student.dat", "wb");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

fwrite(&s1, sizeof(struct Student), 1, fp);


fclose(fp);

printf("Data successfully written to student.dat\n");

return 0;
}

Output:

Data successfully written to student.dat

Explanation:

• fwrite() writes the binary representation of s1 to the file student.dat.


• wb mode is used for binary writing.
• &s1 is the pointer to the structure being written.

Key Points:

• fwrite() is faster than fprintf() because it writes binary data directly.


• It is used for non-text files like images, audio, or binary data structures.
• Files written in binary mode cannot be read using text editors.

Practice Exercise:

• Write a program to input details of multiple students and save them in a binary file using fwrite().

fread() – Reading Binary Data

• fread() reads a block of data from a binary file into memory.


• Syntax:
• size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
o ptr: Pointer to the memory block to store data.
o size: Size of each element.
o count: Number of elements to read.
o stream: File pointer.

Example:

#include <stdio.h>

struct Student {
char name[50];
int age;
float gpa;
};

55
int main() {
FILE *fp;
struct Student s1;

// Reading Binary Data using fread()


fp = fopen("student.dat", "rb");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

fread(&s1, sizeof(struct Student), 1, fp);


fclose(fp);

// Displaying the Data


printf("Student Details:\n");
printf("Name: %s\n", s1.name);
printf("Age: %d\n", s1.age);
printf("GPA: %.2f\n", s1.gpa);

return 0;
}

Output (from previously written file):

Student Details:
Name: Alice
Age: 20
GPA: 3.80

Explanation:

• fread() reads the binary data from student.dat into the structure s1.
• rb mode is used for binary reading.
• &s1 is the pointer to the memory block where data is stored.

Key Points:

• fread() is used to read binary files directly into memory.


• The size of data read depends on the size and count parameters.
• It is faster than fscanf() because no conversion is done.

Practice Exercise:

• Write a program to read and display details of multiple students from a binary file using fread().

8.4 Random Access in Files

• Random Access allows moving the file pointer to any location in a file for reading or writing.
• C provides three functions for random access:
o fseek() – Moves the file pointer to a specific position.
o ftell() – Returns the current position of the file pointer.
o rewind() – Resets the file pointer to the beginning.

56
8.4.1 fseek() – Move File Pointer

• fseek() moves the file pointer to a specific position in the file.


• Syntax:
• int fseek(FILE *stream, long int offset, int whence);
o offset: Number of bytes to move.
o whence: Reference point (SEEK_SET, SEEK_CUR, SEEK_END).

Example:

#include <stdio.h>

int main() {
FILE *fp;
char line[100];

// Writing to File
fp = fopen("sample.txt", "w+");
fputs("First Line\nSecond Line\nThird Line\n", fp);

// Moving to the Second Line


fseek(fp, 11, SEEK_SET);

// Reading from File


fgets(line, sizeof(line), fp);
printf("Read Line: %s", line);

fclose(fp);

return 0;
}

Output:

Read Line: Second Line

Explanation:

• fseek(fp, 11, SEEK_SET); moves the pointer 11 bytes from the start.
• SEEK_SET is the reference point (beginning of the file).

8.4.2 ftell() – Get Current Position

• ftell() returns the current position of the file pointer in bytes from the beginning of the file.
• Useful for determining the current read or write position.
• Syntax:
• long int ftell(FILE *stream);
o stream: File pointer.
o Returns the byte offset from the beginning of the file or -1 on error.

Example:

#include <stdio.h>

int main() {
FILE *fp;
long int pos;
char line[100];

57
// Opening File
fp = fopen("sample.txt", "r");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

// Reading First Line


fgets(line, sizeof(line), fp);
printf("First Line: %s", line);

// Getting Current Position


pos = ftell(fp);
printf("Current Position: %ld\n", pos);

// Reading Second Line


fgets(line, sizeof(line), fp);
printf("Second Line: %s", line);

fclose(fp);

return 0;
}

Output (Assuming sample.txt has multiple lines):

First Line: This is the first line.


Current Position: 24
Second Line: This is the second line.

Explanation:

• ftell(fp); gets the current position of the file pointer after reading the first line.
• The position is 24 bytes from the start, which includes the characters and the newline.
• The position changes as the file is read or written.

Key Points:

• ftell() returns the byte offset from the beginning of the file.
• It helps in tracking the position for random access.
• Returns -1 on error.

Practice Exercise:

• Write a program to read a file line by line and print the file pointer position after each line using
ftell().

8.4.3 rewind() – Reset Pointer to Start

• rewind() resets the file pointer to the beginning of the file.


• Unlike fseek(), it does not return a value and automatically sets the position to 0.
• Syntax:
• void rewind(FILE *stream);
o stream: File pointer.

Example:

58
#include <stdio.h>

int main() {
FILE *fp;
char line[100];

// Opening File
fp = fopen("sample.txt", "r");
if (fp == NULL) {
printf("File could not be opened!\n");
return 1;
}

// Reading First Line


fgets(line, sizeof(line), fp);
printf("First Line: %s", line);

// Rewind to Start
rewind(fp);

// Reading First Line Again


fgets(line, sizeof(line), fp);
printf("First Line Again: %s", line);

fclose(fp);

return 0;
}

Output (Assuming sample.txt has multiple lines):

First Line: This is the first line.


First Line Again: This is the first line.

Explanation:

• rewind(fp); resets the file pointer to the beginning of the file.


• The first line is read twice because the pointer is moved back to the start.

Key Points:

• rewind() resets the pointer without any return value.


• It clears error and end-of-file indicators.
• Useful when you need to re-read a file from the start.

Practice Exercise:

• Write a program to read and display the contents of a file twice using rewind().

8.5 Command Line Arguments

• Command Line Arguments allow users to pass input values when executing a program.
• Useful for creating flexible programs that accept arguments from the terminal or command prompt.
• In C, command line arguments are handled using main() function parameters:
• int main(int argc, char *argv[])
o argc: Argument Count – Number of arguments passed.
o argv: Argument Vector – Array of strings containing the arguments.

59
Example:

#include <stdio.h>

int main(int argc, char *argv[]) {


if (argc < 2) {
printf("Usage: %s <name>\n", argv[0]);
return 1;
}

printf("Hello, %s!\n", argv[1]);


return 0;
}

Command Line Input:

./program_name Alice

Output:

Hello, Alice!

Explanation:

• argc is the argument count (2 in this case).


• argv[0] is the program name (./program_name).
• argv[1] is the first argument (Alice).

Key Points:

• Arguments are passed as strings, so conversions are needed for numerical inputs.
• argc always includes the program name as the first argument.
• Useful for flexible inputs like filenames, configurations, or options.

Practice Exercise:

• Write a program that takes two numbers as command line arguments and prints their sum.

Summary for Module 8:

• ftell() gets the current file pointer position in bytes.


• rewind() resets the pointer to the beginning of the file.
• Command line arguments provide flexible input to programs using argc and argv.
• These tools enable efficient file handling and flexible input methods.

Module 9: Preprocessors and Macros


Duration: 1 Week
Objective: Learn how to use preprocessor directives and macros for efficient and maintainable C programs.

60
Topics Covered:

9.1 Preprocessor Directives

• 9.1.1 #include – Header File Inclusion


• 9.1.2 #define – Macros and Constants
• 9.1.3 #undef – Undefine a Macro
• 9.1.4 #ifdef, #ifndef, #endif – Conditional Compilation
• 9.1.5 #pragma – Special Compiler Instructions

9.2 Macros with Arguments


9.3 Conditional Compilation
9.4 Inline Functions

9.1 Preprocessor Directives

• Preprocessor Directives are instructions to the compiler that are processed before compilation.
• They start with # and are used for including files, defining constants, and conditional compilation.

9.1.1 #include – Header File Inclusion

• #include is used to include header files containing declarations and macros.


• Two types:
o System Header Files: Standard libraries (e.g., <stdio.h>, <stdlib.h>).
o #include <stdio.h>
o User-defined Header Files: Custom header files in the project directory.
o #include "myHeader.h"

Example:

#include <stdio.h>
#include "myHeader.h" // Custom Header File

int main() {
printf("Sum: %d\n", add(10, 20)); // Using function from custom header
return 0;
}

myHeader.h:

int add(int a, int b) {


return a + b;
}

Explanation:

• <stdio.h> is a system header file containing standard input and output functions.
• "myHeader.h" is a user-defined header file containing the add() function.
• Quotes (" ") are used for user-defined files, while angle brackets (< >) are used for system files.

Key Points:

• #include copies the contents of the file into the program before compilation.
• Use angle brackets for standard libraries and quotes for custom headers.

61
• Helps in modularizing code by separating declarations and implementations.

Practice Exercise:

• Create a custom header file with functions for basic arithmetic operations and use them in the main
program.

9.1.2 #define – Macros and Constants

• #define is used to create macros and constants for code reusability and maintainability.
• Two types:
o Object-like Macros: Constants without arguments.
o Function-like Macros: Macros with arguments for inline code substitution.
• Syntax:
• #define NAME value
• #define FUNCTION_MACRO(arguments) expression

Example of Object-like Macros:

#include <stdio.h>

#define PI 3.14159
#define MAX 100

int main() {
printf("Value of PI: %.2f\n", PI);
printf("Maximum Limit: %d\n", MAX);
return 0;
}

Output:

Value of PI: 3.14


Maximum Limit: 100

Example of Function-like Macros:

#include <stdio.h>

#define SQUARE(x) ((x) * (x))


#define MAX(a, b) ((a > b) ? a : b)

int main() {
int num = 5;
printf("Square: %d\n", SQUARE(num));
printf("Maximum: %d\n", MAX(10, 20));
return 0;
}

Output:

Square: 25
Maximum: 20

Explanation:

• #define PI 3.14159 creates a constant for the value of PI.


• #define SQUARE(x) ((x) * (x)) creates a macro function for squaring a number.

62
• Macros are replaced by their values before compilation (inline substitution).

Key Points:

• Macros do not consume memory as they are replaced at compile-time.


• Always use parentheses around macro parameters to ensure correct order of evaluation.
• Macros are faster than functions but lack type checking and debugging support.

Practice Exercise:

• Create macros for common mathematical operations (e.g., CUBE(x), MIN(a, b)).

9.2 Macros with Arguments

• Macros can take arguments for inline code substitution.


• They are faster than functions as they avoid function call overhead.
• Syntax:
• #define MACRO_NAME(parameters) expression

Example:

#include <stdio.h>

#define SUM(a, b) ((a) + (b))


#define MULTIPLY(a, b) ((a) * (b))

int main() {
int x = 5, y = 10;
printf("Sum: %d\n", SUM(x, y));
printf("Product: %d\n", MULTIPLY(x, y));
return 0;
}

Output:

Sum: 15
Product: 50

Explanation:

• SUM(x, y) is replaced with ((x) + (y)) during compilation.


• Parentheses are used to ensure correct evaluation order.

Key Points:

• Macros with arguments are type-independent and support inline expansion.


• No type checking is done, so use with caution.
• Use parentheses to maintain the correct order of evaluation.

Practice Exercise:

• Write macros for ABS(x) (absolute value) and SWAP(a, b) (to swap two variables).

63
9.3 Conditional Compilation

• Conditional compilation allows parts of the code to be included or excluded based on conditions.
• Useful for debugging, platform-specific code, or optional features.
• Directives:
o #ifdef – Checks if a macro is defined.
o #ifndef – Checks if a macro is not defined.
o #if, #elif, #else, #endif – Conditional compilation.

Example:

#include <stdio.h>

#define DEBUG

int main() {
int x = 10;
int y = 20;

#ifdef DEBUG
printf("Debug Mode: x = %d, y = %d\n", x, y);
#endif

int sum = x + y;
printf("Sum: %d\n", sum);

return 0;
}

Output (if DEBUG is defined):

Debug Mode: x = 10, y = 20


Sum: 30

Explanation:

• #ifdef DEBUG checks if DEBUG is defined.


• The block is compiled and executed only if DEBUG is defined.
• To disable debugging, simply comment out or remove #define DEBUG.

9.4 Inline Functions

• Inline Functions are functions expanded inline at compile-time to reduce function call overhead.
• They are faster than normal functions but safer than macros as they provide type checking and debugging
support.
• Syntax:
• inline return_type function_name(parameters) {
• // Function Body
• }

Example:

#include <stdio.h>

// Inline Function
inline int square(int x) {
return x * x;

64
}

int main() {
int num = 5;
printf("Square: %d\n", square(num));
return 0;
}

Output:

Square: 25

Explanation:

• inline keyword suggests the compiler to replace the function call with the function body to avoid call
overhead.
• square(num) is replaced with num * num during compilation.
• Inline functions are type-safe, supporting type checking unlike macros.

Key Points:

• Inline functions are faster than normal functions due to inline expansion.
• They provide type safety and are easier to debug compared to macros.
• The compiler may ignore the inline request for complex functions.
• Suitable for small and frequently called functions.

Practice Exercise:

• Write an inline function to calculate the cube of a number and use it in a program.

Summary for Module 9:

• Preprocessor Directives control the compilation process (e.g., #include, #define, #ifdef).
• Macros provide inline code expansion but lack type safety.
• Conditional Compilation allows including/excluding code for debugging or platform-specific needs.
• Inline Functions combine the speed of macros with the safety of functions.
• These tools enhance code efficiency, maintainability, and readability.

Module 10: Data Structures and Algorithms


Duration: 2 Weeks
Objective: Master essential data structures and algorithms for efficient problem-solving in C.

Topics Covered:

10.1 Dynamic Data Structures

• 10.1.1 Linked Lists (Singly, Doubly, Circular)


• 10.1.2 Stacks and Queues
• 10.1.3 Trees (Binary Tree, Binary Search Tree)

65
10.2 Sorting Algorithms

• 10.2.1 Bubble Sort


• 10.2.2 Selection Sort
• 10.2.3 Merge Sort
• 10.2.4 Quick Sort

10.3 Searching Algorithms

• 10.3.1 Linear Search


• 10.3.2 Binary Search

10.4 Time Complexity Analysis

10.1 Dynamic Data Structures

Dynamic data structures allow flexible memory management and efficient manipulation of data.

10.1.1 Linked Lists

• Linked List is a dynamic data structure where each element (node) points to the next element.
• It can grow and shrink in size dynamically, unlike arrays.
• Types of Linked Lists:
o Singly Linked List – Each node points to the next node.
o Doubly Linked List – Each node points to both the next and previous nodes.
o Circular Linked List – The last node points back to the first node.

Singly Linked List

• A Singly Linked List consists of nodes where each node contains:


o Data – The value stored in the node.
o Pointer – A pointer to the next node.

Structure of Node:

struct Node {
int data;
struct Node* next;
};

Example of Singly Linked List (Insert and Display):

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

// Node Structure
struct Node {
int data;
struct Node* next;
};

// Function to Insert a Node at the End


void insertEnd(struct Node** head, int data) {

66
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;

if (*head == NULL) {
*head = newNode;
return;
}

struct Node* temp = *head;


while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
}

// Function to Display the Linked List


void displayList(struct Node* head) {
struct Node* temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = NULL;

insertEnd(&head, 10);
insertEnd(&head, 20);
insertEnd(&head, 30);

printf("Linked List: ");


displayList(head);

return 0;
}

Output:

Linked List: 10 -> 20 -> 30 -> NULL

Explanation:

• insertEnd() inserts a new node at the end of the linked list.


• displayList() traverses the linked list and prints all nodes.
• Memory is dynamically allocated using malloc() for each node.

Key Points:

• Linked lists allow dynamic memory allocation and efficient insertion/deletion.


• No contiguous memory required, unlike arrays.
• Accessing elements is slower (O(n)) compared to arrays (O(1)).

Practice Exercise:

• Implement a singly linked list with functions to insert at the beginning and delete a node by value.

67
10.1.1.2 Doubly Linked List
• A Doubly Linked List consists of nodes where each node has:
o Data – The value stored in the node.
o Pointer to Next Node (next) – Points to the next node.
o Pointer to Previous Node (prev) – Points to the previous node.

Structure of Node:

struct Node {
int data;
struct Node* next;
struct Node* prev;
};

Example of Doubly Linked List (Insert and Display):

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

// Node Structure
struct Node {
int data;
struct Node* next;
struct Node* prev;
};

// Function to Insert at the End


void insertEnd(struct Node** head, int data) {
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;

if (*head == NULL) {
*head = newNode;
return;
}

struct Node* temp = *head;


while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
newNode->prev = temp;
}

// Function to Display the Doubly Linked List


void displayList(struct Node* head) {
struct Node* temp = head;
printf("Doubly Linked List: ");
while (temp != NULL) {
printf("%d <-> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = NULL;

insertEnd(&head, 10);
insertEnd(&head, 20);
insertEnd(&head, 30);

68
displayList(head);

return 0;
}

Output:

Doubly Linked List: 10 <-> 20 <-> 30 <-> NULL

Explanation:

• insertEnd() adds a new node at the end of the doubly linked list.
• newNode->prev = temp; connects the new node to the previous node.
• The list can be traversed in both directions because each node points to the next and previous
nodes.

Key Points:

• Doubly linked lists allow bidirectional traversal.


• Efficient deletion because the previous node pointer is readily available.
• More memory usage than singly linked lists due to the additional prev pointer.

Practice Exercise:

• Implement a doubly linked list with functions to delete a node and reverse the list.

10.1.1.3 Circular Linked List


• A Circular Linked List is a linked list where the last node points back to the first node.
• It can be Singly Circular (only next pointer) or Doubly Circular (next and prev pointers).

Example of Circular Linked List (Insert and Display):

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

// Node Structure
struct Node {
int data;
struct Node* next;
};

// Function to Insert at the End


void insertEnd(struct Node** head, int data) {
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = *head;

if (*head == NULL) {
*head = newNode;
newNode->next = *head;
return;
}

struct Node* temp = *head;


while (temp->next != *head) {

69
temp = temp->next;
}
temp->next = newNode;
}

// Function to Display Circular Linked List


void displayList(struct Node* head) {
if (head == NULL) return;

struct Node* temp = head;


printf("Circular Linked List: ");
do {
printf("%d -> ", temp->data);
temp = temp->next;
} while (temp != head);
printf("(Head)\n");
}

int main() {
struct Node* head = NULL;

insertEnd(&head, 10);
insertEnd(&head, 20);
insertEnd(&head, 30);

displayList(head);

return 0;
}

Output:

Circular Linked List: 10 -> 20 -> 30 -> (Head)

Explanation:

• insertEnd() adds a new node at the end and connects it back to the head.
• temp->next = newNode; links the last node to the new node.
• newNode->next = *head; makes the list circular by connecting the last node to the first node.
• The loop uses do...while to ensure the head is printed.

Key Points:

• Circular linked lists are useful for cyclic data structures (e.g., round-robin scheduling).
• They allow continuous traversal without null checks.
• Efficient for implementing circular buffers.

Practice Exercise:

• Implement a circular linked list with functions to insert at the beginning and delete a node.

10.1.2 Stacks and Queues


• Stack and Queue are linear data structures used for storing and accessing data in a specific order.
• Stack: Follows LIFO (Last In, First Out) principle.
• Queue: Follows FIFO (First In, First Out) principle.

70
10.1.2.1 Stack

• A Stack stores data in a LIFO order, meaning the last element added is the first one removed.
• Operations:
o Push: Add an element to the top of the stack.
o Pop: Remove the top element.
o Peek: Access the top element without removing it.
o isEmpty: Check if the stack is empty.
o isFull: Check if the stack is full (in array implementation).

Stack Using Array

• An array-based implementation of stack uses a fixed-size array and a variable top to keep track of the
stack’s top element.

Example:

#include <stdio.h>
#define MAX 5 // Maximum size of the stack

int stack[MAX];
int top = -1;

// Push Function
void push(int data) {
if (top == MAX - 1) {
printf("Stack Overflow!\n");
return;
}
stack[++top] = data;
printf("%d pushed to stack\n", data);
}

// Pop Function
int pop() {
if (top == -1) {
printf("Stack Underflow!\n");
return -1;
}
return stack[top--];
}

// Peek Function
int peek() {
if (top == -1) {
printf("Stack is Empty!\n");
return -1;
}
return stack[top];
}

// Display Stack
void display() {
if (top == -1) {
printf("Stack is Empty!\n");
return;
}
printf("Stack: ");
for (int i = top; i >= 0; i--) {
printf("%d ", stack[i]);
}
printf("\n");

71
}

int main() {
push(10);
push(20);
push(30);
display();
printf("Peek: %d\n", peek());
printf("Popped: %d\n", pop());
display();

return 0;
}

Output:

10 pushed to stack
20 pushed to stack
30 pushed to stack
Stack: 30 20 10
Peek: 30
Popped: 30
Stack: 20 10

Explanation:

• push() adds elements to the top of the stack.


• pop() removes the top element from the stack.
• peek() returns the top element without removing it.
• Array-based implementation has a fixed size and may face stack overflow if full.

Key Points:

• Array-based stacks are simple and fast but have a fixed size limit.
• Overflow occurs when pushing into a full stack.
• Underflow occurs when popping from an empty stack.

Practice Exercise:

• Implement a stack with array-based functions to check if the stack is empty and reverse a string.

Stack Using Linked List

• A Linked List-based implementation provides dynamic memory allocation, allowing the stack to grow and
shrink as needed.

Example:

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

// Node Structure
struct Node {
int data;
struct Node* next;
};

struct Node* top = NULL;

72
// Push Function
void push(int data) {
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = top;
top = newNode;
printf("%d pushed to stack\n", data);
}

// Pop Function
int pop() {
if (top == NULL) {
printf("Stack Underflow!\n");
return -1;
}
int data = top->data;
struct Node* temp = top;
top = top->next;
free(temp);
return data;
}

// Peek Function
int peek() {
if (top == NULL) {
printf("Stack is Empty!\n");
return -1;
}
return top->data;
}

// Display Stack
void display() {
struct Node* temp = top;
if (temp == NULL) {
printf("Stack is Empty!\n");
return;
}
printf("Stack: ");
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}

int main() {
push(10);
push(20);
push(30);
display();
printf("Peek: %d\n", peek());
printf("Popped: %d\n", pop());
display();

return 0;
}

Output:

10 pushed to stack
20 pushed to stack
30 pushed to stack
Stack: 30 20 10
Peek: 30
Popped: 30

73
Stack: 20 10

Explanation:

• push() dynamically allocates memory for each new node and adds it to the top of the stack.
• pop() removes the top node and frees its memory.
• top always points to the last inserted node.

Key Points:

• No size limit due to dynamic memory allocation.


• Efficient for variable-sized stacks.
• More memory overhead due to pointers in each node.

Practice Exercise:

• Implement a linked list-based stack with a function to find the minimum element in the stack.

10.1.2.2 Queue
• A Queue stores data in FIFO (First In, First Out) order.
• Operations:
o Enqueue: Add an element at the end.
o Dequeue: Remove an element from the front.
o Front: Access the front element without removing it.
o isEmpty: Check if the queue is empty.
o isFull: Check if the queue is full (in array implementation).

Queue Using Array

• An array-based implementation of queue uses a fixed-size array with front and rear pointers.

Example:

#include <stdio.h>
#define MAX 5

int queue[MAX];
int front = -1, rear = -1;

// Enqueue Function
void enqueue(int data) {
if (rear == MAX - 1) {
printf("Queue Overflow!\n");
return;
}
if (front == -1) front = 0;
queue[++rear] = data;
printf("%d enqueued\n", data);
}

// Dequeue Function

74
int dequeue() {
if (front == -1 || front > rear) {
printf("Queue Underflow!\n");
return -1;
}
return queue[front++];
}

// Display Queue
void display() {
if (front == -1 || front > rear) {
printf("Queue is Empty!\n");
return;
}
printf("Queue: ");
for (int i = front; i <= rear; i++) {
printf("%d ", queue[i]);
}
printf("\n");
}

int main() {
enqueue(10);
enqueue(20);
enqueue(30);
display();
printf("Dequeued: %d\n", dequeue());
display();

return 0;
}

Output:

10 enqueued
20 enqueued
30 enqueued
Queue: 10 20 30
Dequeued: 10
Queue: 20 30

Explanation:

• enqueue() adds elements to the end of the queue (rear).


• dequeue() removes elements from the front of the queue (front).
• Array-based implementation can lead to wasted space due to fixed size.

Practice Exercise:

• Implement a queue with array-based functions to check if the queue is empty and reverse the queue.

Queue Using Linked List

• A Linked List-based implementation allows dynamic memory allocation, enabling the queue to grow and
shrink as needed.

Example:

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

75
// Node Structure
struct Node {
int data;
struct Node* next;
};

struct Node *front = NULL, *rear = NULL;

// Enqueue Function
void enqueue(int data) {
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;

if (rear == NULL) {
front = rear = newNode;
} else {
rear->next = newNode;
rear = newNode;
}
printf("%d enqueued\n", data);
}

// Dequeue Function
int dequeue() {
if (front == NULL) {
printf("Queue Underflow!\n");
return -1;
}
int data = front->data;
struct Node* temp = front;
front = front->next;
free(temp);

if (front == NULL) rear = NULL;


return data;
}

// Display Queue
void display() {
struct Node* temp = front;
if (temp == NULL) {
printf("Queue is Empty!\n");
return;
}
printf("Queue: ");
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}

int main() {
enqueue(10);
enqueue(20);
enqueue(30);
display();
printf("Dequeued: %d\n", dequeue());
display();

return 0;
}

Output:

76
10 enqueued
20 enqueued
30 enqueued
Queue: 10 20 30
Dequeued: 10
Queue: 20 30

Explanation:

• enqueue() dynamically allocates memory and adds the node at the end.
• dequeue() removes the front node and frees its memory.
• rear always points to the last node, while front points to the first node.

Key Points:

• No size limit due to dynamic memory allocation.


• Efficient for variable-sized queues.
• More memory overhead due to pointers in each node.

Practice Exercise:

• Implement a linked list-based queue with functions to find the maximum element in the queue.

10.1.2.3 Circular Queue


• A Circular Queue is a queue in which the last position is connected back to the first position to
form a circle.
• It efficiently uses the array space by recycling the slots freed by dequeued elements.
• This eliminates the space wastage problem in the linear array implementation.
• Operations:
o Enqueue: Add an element at the rear (circularly).
o Dequeue: Remove an element from the front.
o isFull: Check if the queue is full.
o isEmpty: Check if the queue is empty.

Circular Queue Using Array

• A Circular Queue uses a fixed-size array with front and rear pointers that wrap around when
they reach the end of the array.
• Circular Increment:
• rear = (rear + 1) % MAX;
• front = (front + 1) % MAX;

Example:

#include <stdio.h>
#define MAX 5 // Maximum size of the queue

int queue[MAX];
int front = -1, rear = -1;

// Check if Queue is Full


int isFull() {

77
return (front == (rear + 1) % MAX);
}

// Check if Queue is Empty


int isEmpty() {
return (front == -1);
}

// Enqueue Function
void enqueue(int data) {
if (isFull()) {
printf("Queue Overflow!\n");
return;
}
if (isEmpty()) {
front = rear = 0;
} else {
rear = (rear + 1) % MAX;
}
queue[rear] = data;
printf("%d enqueued\n", data);
}

// Dequeue Function
int dequeue() {
if (isEmpty()) {
printf("Queue Underflow!\n");
return -1;
}
int data = queue[front];
if (front == rear) {
front = rear = -1;
} else {
front = (front + 1) % MAX;
}
return data;
}

// Display Circular Queue


void display() {
if (isEmpty()) {
printf("Queue is Empty!\n");
return;
}
printf("Circular Queue: ");
int i = front;
do {
printf("%d ", queue[i]);
i = (i + 1) % MAX;
} while (i != (rear + 1) % MAX);
printf("\n");
}

int main() {
enqueue(10);
enqueue(20);
enqueue(30);
enqueue(40);
display();
printf("Dequeued: %d\n", dequeue());
display();
enqueue(50);
enqueue(60); // Wrap-around
display();

return 0;
}

78
Output:

10 enqueued
20 enqueued
30 enqueued
40 enqueued
Circular Queue: 10 20 30 40
Dequeued: 10
Circular Queue: 20 30 40
50 enqueued
60 enqueued
Circular Queue: 20 30 40 50 60

Explanation:

• enqueue() adds elements to the rear using circular increment.


• dequeue() removes elements from the front and moves the front pointer circularly.
• The queue wraps around when the end of the array is reached, utilizing freed slots.
• Overflow Condition: When front is just ahead of rear in a circular manner.

Key Points:

• Circular Queue efficiently uses array space by recycling slots.


• No shifting of elements is required, unlike a linear queue.
• Suitable for buffer management, e.g., CPU scheduling, IO buffers.

Practice Exercise:

• Implement a circular queue with array-based functions to find the size of the queue.

10.1.3 Trees
• A Tree is a non-linear data structure with a hierarchical relationship between its elements (nodes).
• Binary Tree: Each node has at most two children (left and right).
• Binary Search Tree (BST): A binary tree with an ordering property:
o Left Subtree: Contains nodes with values less than the parent node.
o Right Subtree: Contains nodes with values greater than the parent node.

10.1.3.1 Binary Tree

• A Binary Tree is a tree in which each node has at most two children (left and right).

Structure of Node:

struct Node {
int data;
struct Node* left;
struct Node* right;
};

Example of Binary Tree (Creation and Traversal):

#include <stdio.h>

79
#include <stdlib.h>

// Node Structure
struct Node {
int data;
struct Node* left;
struct Node* right;
};

// Function to Create a New Node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->left = newNode->right = NULL;
return newNode;
}

// Preorder Traversal (Root -> Left -> Right)


void preorder(struct Node* root) {
if (root != NULL) {
printf("%d ", root->data);
preorder(root->left);
preorder(root->right);
}
}

int main() {
// Creating Binary Tree
struct Node* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);

printf("Preorder Traversal: ");


preorder(root);
printf("\n");

return 0;
}

Output:

Preorder Traversal: 1 2 4 5 3

10.1.3.2 Binary Search Tree (BST)


• A Binary Search Tree (BST) is a binary tree with the following properties:
o Left Subtree: Contains nodes with values less than the parent node.
o Right Subtree: Contains nodes with values greater than the parent node.
o No Duplicate Values: Each value is unique.
• Operations:
o Insertion: Add a new node at the correct position.
o Deletion: Remove a node and rearrange the tree.
o Search: Efficiently find a node using the ordering property.
o Traversal: Inorder, Preorder, Postorder.

Structure of Node:

struct Node {

80
int data;
struct Node* left;
struct Node* right;
};

Insertion in Binary Search Tree

• Start at the root node.


• Compare the value to be inserted with the current node:
o If less, go to the left subtree.
o If greater, go to the right subtree.
• Repeat until the correct position is found, then insert the new node.

Example of Insertion and Inorder Traversal:

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

// Node Structure
struct Node {
int data;
struct Node* left;
struct Node* right;
};

// Function to Create a New Node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->left = newNode->right = NULL;
return newNode;
}

// Function to Insert a Node in BST


struct Node* insert(struct Node* root, int data) {
if (root == NULL) return createNode(data);

if (data < root->data) {


root->left = insert(root->left, data);
} else if (data > root->data) {
root->right = insert(root->right, data);
}

return root;
}

// Inorder Traversal (Left -> Root -> Right)


void inorder(struct Node* root) {
if (root != NULL) {
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
}

int main() {
struct Node* root = NULL;

// Inserting Nodes
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 70);
root = insert(root, 20);

81
root = insert(root, 40);
root = insert(root, 60);
root = insert(root, 80);

printf("Inorder Traversal: ");


inorder(root);
printf("\n");

return 0;
}

Output:

Inorder Traversal: 20 30 40 50 60 70 80

Explanation:

• insert() function places the node at the correct position using the BST property.
• inorder() traversal displays the nodes in ascending order.
• The nodes are recursively visited in the order: Left, Root, Right.

Key Points:

• Inorder traversal of a BST always gives sorted order.


• Time Complexity:
o Average Case: O(log n) for balanced trees.
o Worst Case: O(n) for skewed trees.

Practice Exercise:

• Implement a function to search for a node in a Binary Search Tree.

10.2 Sorting Algorithms


Sorting is the process of arranging elements in a specific order (ascending or descending).

• Comparison-Based Sorting: Compares elements to determine their order (e.g., Bubble Sort, Quick
Sort).
• Non-Comparison Sorting: Uses other information, such as counting (e.g., Counting Sort, not
covered here).

10.2.1 Bubble Sort

• Bubble Sort repeatedly compares adjacent elements and swaps them if they are in the wrong order.
• It "bubbles" the largest element to the end of the array in each pass.
• Time Complexity: O(n²)
• Space Complexity: O(1) (In-place sorting)

Example:

#include <stdio.h>

82
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// Swap
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// Function to Display Array


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

int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");


display(arr, n);

bubbleSort(arr, n);

printf("Sorted Array: ");


display(arr, n);

return 0;
}

Output:

Original Array: 64 34 25 12 22 11 90
Sorted Array: 11 12 22 25 34 64 90

Explanation:

• Adjacent elements are compared and swapped if needed.


• The largest element "bubbles" to the end in each pass.
• After each pass, the last i elements are sorted and no longer considered.

Key Points:

• Bubble Sort is simple but inefficient for large datasets.


• Stable Sort: Maintains the relative order of equal elements.
• Best used for small arrays or nearly sorted arrays.

Practice Exercise:

• Modify the Bubble Sort to stop early if no swaps occur in a pass (Optimized Bubble Sort).

10.2.2 Selection Sort


83
• Selection Sort repeatedly selects the smallest (or largest) element from the unsorted portion and
places it at the correct position.
• It divides the array into sorted and unsorted portions.
• Time Complexity: O(n²)
• Space Complexity: O(1) (In-place sorting)

How It Works:

1. Start from the first element and find the minimum element in the unsorted portion.
2. Swap the minimum element with the first element.
3. Move the boundary between the sorted and unsorted portions one step to the right.
4. Repeat until the entire array is sorted.

Example of Selection Sort:

#include <stdio.h>

// Selection Sort Function


void selectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// Swap the found minimum element with the first element
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}

// Function to Display Array


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

int main() {
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");


display(arr, n);

selectionSort(arr, n);

printf("Sorted Array: ");


display(arr, n);

return 0;
}

Output:

Original Array: 64 25 12 22 11
Sorted Array: 11 12 22 25 64

84
Explanation:

• In each iteration, the minimum element is selected and swapped with the current position.
• The sorted portion grows from left to right.
• The minimum index is updated whenever a smaller element is found.

Key Points:

• In-place sorting with no additional memory usage.


• Not Stable: Does not preserve the relative order of equal elements.
• Inefficient for large datasets due to O(n²) complexity.

Practice Exercise:

• Modify the Selection Sort to find the maximum element and sort in descending order.

10.2.3 Merge Sort


• Merge Sort is a Divide and Conquer sorting algorithm that divides the array into smaller subarrays,
recursively sorts them, and then merges them.
• It divides the array into two halves, sorts each half, and then merges them back in sorted order.
• Time Complexity: O(n log n)
• Space Complexity: O(n) (Requires temporary arrays for merging)

How It Works:

1. Divide the array into two halves until each subarray contains a single element.
2. Conquer by recursively sorting the subarrays.
3. Merge the sorted subarrays to produce a single sorted array.

Example of Merge Sort:

#include <stdio.h>

// Merge Function
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;

// Temporary Arrays
int L[n1], R[n2];

// Copy Data to Temporary Arrays


for (int i = 0; i < n1; i++) L[i] = arr[left + i];
for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];

// Merge Temporary Arrays


int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k++] = L[i++];
} else {
arr[k++] = R[j++];
}

85
}

// Copy Remaining Elements


while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}

// Merge Sort Function


void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;

// Recursively Sort First and Second Halves


mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);

// Merge Sorted Halves


merge(arr, left, mid, right);
}
}

// Function to Display Array


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

int main() {
int arr[] = {38, 27, 43, 3, 9, 82, 10};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");


display(arr, n);

mergeSort(arr, 0, n - 1);

printf("Sorted Array: ");


display(arr, n);

return 0;
}

Output:

Original Array: 38 27 43 3 9 82 10
Sorted Array: 3 9 10 27 38 43 82

Explanation:

• mergeSort() recursively divides the array until subarrays contain a single element.
• merge() merges the sorted subarrays back in sorted order.
• It efficiently uses the divide and conquer approach for faster sorting.

Key Points:

• Stable Sort: Maintains the relative order of equal elements.


• Efficient for large datasets with O(n log n) complexity.
• Requires additional memory for temporary arrays during merging.

Practice Exercise:

86
• Modify the Merge Sort to sort in descending order.

10.2.4 Quick Sort


• Quick Sort is a Divide and Conquer sorting algorithm that selects a pivot element and partitions
the array into two halves:
o Left Subarray: Elements less than the pivot.
o Right Subarray: Elements greater than the pivot.
• The process is recursively repeated for each subarray.
• Time Complexity:
o Best and Average Case: O(n log n)
o Worst Case: O(n²) (Occurs when the array is already sorted or nearly sorted)
• Space Complexity: O(log n) due to recursive function calls.

How It Works:

1. Choose a Pivot: Select an element as the pivot (commonly the last element).
2. Partitioning:
o Rearrange the array so that elements less than the pivot are on the left and elements greater
are on the right.
o Place the pivot in its correct sorted position.
3. Recursively Apply:
o Apply the same logic to the left and right subarrays.

Example of Quick Sort:

#include <stdio.h>

// Function to Swap Two Elements


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

// Partition Function
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // Choosing the last element as pivot
int i = low - 1;

for (int j = low; j < high; j++) {


if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return i + 1;
}

// Quick Sort Function


void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);

// Recursively Sort Elements Before and After Partition

87
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}

// Function to Display Array


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

int main() {
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");


display(arr, n);

quickSort(arr, 0, n - 1);

printf("Sorted Array: ");


display(arr, n);

return 0;
}

Output:

Original Array: 10 7 8 9 1 5
Sorted Array: 1 5 7 8 9 10

Explanation:

• partition() selects the last element as the pivot and partitions the array.
• Elements less than the pivot are moved to the left, and greater elements are moved to the right.
• quickSort() recursively applies the partitioning to the left and right subarrays.

Key Points:

• In-place sorting with no additional memory usage.


• Not Stable: Relative order of equal elements is not maintained.
• Efficient for large datasets with O(n log n) complexity in the average case.

Practice Exercise:

• Implement Quick Sort using the first element as the pivot.

10.3 Searching Algorithms


Searching algorithms are used to find the position of a target value within a list or array.

• Linear Search: Sequentially checks each element until the target is found.
• Binary Search: Efficiently finds the target by repeatedly dividing the sorted array in half.

88
10.3.1 Linear Search

• Linear Search sequentially checks each element in the array until the target value is found or the
end is reached.
• Time Complexity: O(n)
• Space Complexity: O(1)

Example of Linear Search:

#include <stdio.h>

// Linear Search Function


int linearSearch(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}

int main() {
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 30;

int result = linearSearch(arr, n, target);

if (result == -1) {
printf("Element not found\n");
} else {
printf("Element found at index: %d\n", result);
}

return 0;
}

Output:

Element found at index: 2

Key Points:

• Works on both sorted and unsorted arrays.


• Inefficient for large datasets due to O(n) complexity.
• Best used for small arrays or when the array is unsorted.

Practice Exercise:

• Modify the Linear Search to find all occurrences of the target value.

10.3.2 Binary Search

• Binary Search efficiently finds the target by repeatedly dividing the sorted array in half.
• The array must be sorted before performing binary search.
• Time Complexity: O(log n)
• Space Complexity: O(1) (Iterative) or O(log n) (Recursive)

89
10.3.2 Binary Search
• Binary Search efficiently finds the target by repeatedly dividing the sorted array in half.
• It compares the target value to the middle element of the array:
o If equal, the target is found.
o If less, search the left half.
o If greater, search the right half.
• Time Complexity: O(log n)
• Space Complexity:
o O(1) for Iterative Implementation.
o O(log n) for Recursive Implementation (due to call stack).

Requirements:

• The array must be sorted before performing binary search.

Iterative Binary Search

• Iterative Binary Search uses a loop to repeatedly divide the search range in half.
• It keeps track of the search range using low and high pointers.

Example of Iterative Binary Search:

#include <stdio.h>

// Iterative Binary Search Function


int binarySearch(int arr[], int n, int target) {
int low = 0, high = n - 1;

while (low <= high) {


int mid = low + (high - low) / 2;

// Check if target is present at mid


if (arr[mid] == target) {
return mid;
}

// If target is greater, ignore left half


if (arr[mid] < target) {
low = mid + 1;
}
// If target is smaller, ignore right half
else {
high = mid - 1;
}
}

// Target not found


return -1;
}

int main() {
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 30;

90
int result = binarySearch(arr, n, target);

if (result == -1) {
printf("Element not found\n");
} else {
printf("Element found at index: %d\n", result);
}

return 0;
}

Output:

Element found at index: 2

Explanation:

• low and high pointers define the current search range.


• mid is calculated as low + (high - low) / 2 to avoid overflow.
• The search range is halved after each comparison.

Key Points:

• Efficient for large sorted datasets with O(log n) complexity.


• Requires the array to be sorted.
• Iterative approach is memory efficient with O(1) space complexity.

Practice Exercise:

• Modify the Iterative Binary Search to find the first occurrence of the target in a sorted array with
duplicate elements.

Recursive Binary Search

• Recursive Binary Search uses recursion to repeatedly divide the search range in half.
• It follows the same logic as the iterative approach but uses the call stack to manage the search range.

Example of Recursive Binary Search:

#include <stdio.h>

// Recursive Binary Search Function


int binarySearchRecursive(int arr[], int low, int high, int target) {
if (low <= high) {
int mid = low + (high - low) / 2;

// Check if target is present at mid


if (arr[mid] == target) {
return mid;
}

// If target is greater, search right half


if (arr[mid] < target) {
return binarySearchRecursive(arr, mid + 1, high, target);
}
// If target is smaller, search left half
else {
return binarySearchRecursive(arr, low, mid - 1, target);

91
}
}

// Target not found


return -1;
}

int main() {
int arr[] = {2, 4, 6, 8, 10, 12, 14};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 10;

int result = binarySearchRecursive(arr, 0, n - 1, target);

if (result == -1) {
printf("Element not found\n");
} else {
printf("Element found at index: %d\n", result);
}

return 0;
}

Output:

Element found at index: 4

Explanation:

• binarySearchRecursive() repeatedly divides the array in half using recursion.


• It uses call stack frames to remember the state of each recursive call.
• The search range is narrowed down until the target is found or the range is exhausted.

Key Points:

• Elegant and concise code using recursion.


• Memory overhead due to recursive calls (O(log n) space complexity).
• Useful when elegance and simplicity are preferred over memory efficiency.

Practice Exercise:

• Implement Recursive Binary Search to find the last occurrence of the target in a sorted array with
duplicate elements.

10.4 Time Complexity Analysis


• Time Complexity measures the execution time of an algorithm as a function of the input size n.
• It helps in predicting the performance of algorithms on large datasets.
• Big O Notation (O) is used to express the upper bound of an algorithm's runtime.
• Common Time Complexities:
o O(1) – Constant Time
o O(log n) – Logarithmic Time (e.g., Binary Search)
o O(n) – Linear Time (e.g., Linear Search)
o O(n log n) – Log-Linear Time (e.g., Merge Sort, Quick Sort)
o O(n²) – Quadratic Time (e.g., Bubble Sort, Selection Sort)
o O(2^n) – Exponential Time (e.g., Recursive Fibonacci)

92
Practice Exercise:

• Analyze the time complexity of the algorithms implemented in this module.

Summary for Module 10:

• Mastered sorting algorithms (Bubble Sort, Selection Sort, Merge Sort, Quick Sort).
• Learned searching algorithms (Linear Search, Binary Search).
• Gained insights into Time Complexity Analysis for evaluating algorithm efficiency.
• These skills enable you to efficiently solve complex problems in C.

Completing this module will give you:


• Expert-level command over C programming.
• Strong problem-solving and analytical skills.
• Proficiency in data structures, algorithms, and time complexity
analysis.
• Ability to build real-world applications from scratch.
• Career opportunities in system programming, embedded systems,
competitive programming, and software development
For any types of notes Contact me here:

[email protected]

93

You might also like