Mastering C Language 2
Mastering C Language 2
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
Topics Covered:
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.
Example:
#include <stdio.h>
int main() {
int num = 10;
printf("Number: %d\n", num);
return 0;
}
2
1.5 Input and Output Functions
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;
}
• Single-line Comment: //
• Multi-line Comment: /* ... */
Good Practices:
Duration: 1 Week
Objective: Understand how to perform operations and form expressions using C operators.
Topics Covered:
3
2.6 Conditional Operator (?:)
2.7 Operator Precedence and Associativity
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:
Practice Exercise:
• Write a program to calculate the area and circumference of a circle using arithmetic operators.
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:
Practice Exercise:
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:
Practice Exercise:
• Write a program to check if a number is within a given range using logical operators.
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:
Practice Exercise:
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 (?:)
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;
}
Example:
• 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.
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
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.
Topics Covered:
• 3.1.1 if Statement
• 3.1.2 if-else Statement
• 3.1.3 if-else if-else Ladder
• 3.1.4 switch Statement
8
• 3.4.3 goto Statement
Conditional statements allow programs to make decisions and execute certain blocks of code based on
specified conditions.
3.1.1 if Statement
Example:
#include <stdio.h>
int main() {
int num = 10;
if (num > 5) {
printf("Number is greater than 5\n");
}
return 0;
}
Output:
Key Points:
Practice Exercise:
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:
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:
Practice Exercise:
• Write a program to determine the day of the week based on a number (1 for Sunday, 2 for Monday, etc.).
• 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:
Practice Exercise:
11
• Write a calculator program using switch to perform basic arithmetic operations (+, -, *, /).
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:
Practice Exercise:
Loops allow the repetition of a block of code multiple times based on a condition.
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:
Key Points:
Practice Exercise:
• Write a program to calculate the sum of digits of a number using a 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:
Key Points:
Practice Exercise:
• Implement a menu-driven program to perform basic arithmetic operations using a do-while loop.
#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:
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.
Jump statements alter the normal flow of control in loops and conditional blocks.
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:
Practice Exercise:
• Implement a program to search for an element in an array using break to exit once the element is found.
• 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.
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
16
• Loops enable repetition of blocks of code.
• Jump statements alter the flow of control.
Topics Covered:
• 4.5.1 auto
• 4.5.2 register
• 4.5.3 static
• 4.5.4 extern
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:
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:
• 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.
Functions can take input values through parameters. In C, parameters can be passed in two ways:
Example:
#include <stdio.h>
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:
Practice Exercise:
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>
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:
Explanation:
Key Points:
Practice Exercise:
• Write a function to find the sum of elements in an array using call by reference.
• 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.
#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:
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:
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()
• 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:
Example:
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
return 0;
}
Output:
Element 0: 10
Element 1: 20
Element 2: 30
Element 3: 40
Element 4: 50
Key Points:
Practice Exercise:
22
• Write a program to find the maximum and minimum elements in an array.
• 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}
};
return 0;
}
Output:
1 2 3
4 5 6
Key Points:
Practice Exercise:
5.3 Strings in C
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:
Output:
Hello, Alice!
Key Points:
Practice Exercise:
C provides several built-in functions in the <string.h> library for manipulating strings.
5.4.1 strlen()
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()
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:
5.4.3 strcmp()
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:
5.4.4 strcat()
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:
• 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
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:
Practice Exercise:
Example:
#include <stdio.h>
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:
Duration: 2 Weeks
Objective: Master the use of pointers, pointer arithmetic, and dynamic memory allocation for efficient
memory management.
Topics Covered:
• 6.5.1 malloc()
• 6.5.2 calloc()
• 6.5.3 realloc()
• 6.5.4 free()
28
• pointer_name = &variable;
Example:
Example:
#include <stdio.h>
int main() {
int num = 10;
int *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:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30};
int *ptr = arr;
return 0;
}
Key Points:
Practice Exercise:
29
• Write a program to calculate the sum of an array using pointers and pointer arithmetic.
Example:
#include <stdio.h>
int main() {
int numbers[] = {10, 20, 30, 40, 50};
int *ptr = numbers; // Pointer to the first element
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:
Practice Exercise:
30
Example:
#include <stdio.h>
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:
Example:
#include <stdio.h>
// Functions
int add(int a, int b) {
return a + b;
}
int main() {
31
// Declaring Function Pointers
int (*funcPtr)(int, int);
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:
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;
return 0;
}
Output:
Name: Alice
Age: 20
GPA: 3.80
Explanation:
Key Points:
Practice Exercise:
• Write a program to input and display details of multiple students using an array of structure pointers.
• 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>
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
33
int n;
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
return 0;
}
Output:
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.
34
• pointer = (data_type*) calloc(num_elements, size_of_each_element);
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n;
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
return 0;
}
Output:
Explanation:
Key Points:
Practice Exercise:
• 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]);
}
return 0;
}
Output:
Enter 3 elements:
10 20 30
Enter 2 more elements:
40 50
Array Elements: 10 20 30 40 50
Explanation:
Key Points:
Practice Exercise:
36
• Implement a dynamic list that grows as the user inputs more elements using realloc().
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n;
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
return 0;
}
Output:
Explanation:
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.
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.
• 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.
Topics Covered:
• 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;
• };
#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:
Practice Exercise:
• Create a structure for employee details (name, ID, salary) and input/display details of multiple
employees.
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;
• };
#include <stdio.h>
struct Student {
char name[50];
int age;
struct Address addr;
};
int main() {
// Initializing Nested Structure
struct Student s1 = {"Bob", 21, {"New York", "NY"}};
return 0;
}
Output:
Name: Bob
Age: 21
City: New York
State: NY
Explanation:
Key Points:
Practice Exercise:
40
• Create a nested structure for a book's details (title, author, and publication address).
Example:
#include <stdio.h>
// Defining Structure
struct Student {
char name[50];
int age;
float gpa;
};
int main() {
// Declaring Array of Structures
struct Student students[3];
return 0;
}
Output:
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:
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.
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;
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:
#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:
Practice Exercise:
• Create a union to store different data types (int, float, char) and display their values to observe
memory sharing.
• 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.
• 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:
Example:
#include <stdio.h>
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.
• 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:
Example:
#include <stdio.h>
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).
Key Points:
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.
• 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.
46
Duration: 1 Week
Objective: Learn how to perform input and output operations with files for data storage and retrieval.
Topics Covered:
• 8.4.1 fseek()
• 8.4.2 ftell()
• 8.4.3 rewind()
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);
}
• 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.
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;
return 0;
}
Output:
48
Enter GPA: 3.8
Data successfully written to student.txt
Explanation:
Key Points:
Practice Exercise:
• Write a program to input details of multiple books (title, author, price) and store them in a file using
fprintf().
• 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;
49
return 0;
}
Student Details:
Name: Alice
Age: 20
GPA: 3.80
Explanation:
Key Points:
Practice Exercise:
• Write a program to read and display the details of multiple books stored in a file.
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.
50
• Syntax:
• FILE *fp = fopen("filename.txt", "a");
Example:
#include <stdio.h>
int main() {
FILE *fp;
char text[100];
// Writing to File
fprintf(fp, "Hello, World!\n");
fclose(fp);
return 0;
}
Output:
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.
Example:
#include <stdio.h>
int main() {
FILE *fp;
char text[100];
// Appending to File
fprintf(fp, "Appending this line.\n");
fclose(fp);
return 0;
}
File Content:
Hello, World!
Appending this line.
Explanation:
52
Key Points:
Practice Exercise:
• Write a program to log user inputs to a file using a+ mode, preserving previous entries.
Syntax:
Example:
#include <stdio.h>
int main() {
FILE *fp;
char line[100];
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:
Key Points:
Practice Exercise:
• Write a program to read and display a file's contents line by line using fgets().
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};
return 0;
}
Output:
Explanation:
Key Points:
Practice Exercise:
• Write a program to input details of multiple students and save them in a binary file using fwrite().
Example:
#include <stdio.h>
struct Student {
char name[50];
int age;
float gpa;
};
55
int main() {
FILE *fp;
struct Student s1;
return 0;
}
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:
Practice Exercise:
• Write a program to read and display details of multiple students from a binary file using fread().
• 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
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);
fclose(fp);
return 0;
}
Output:
Explanation:
• fseek(fp, 11, SEEK_SET); moves the pointer 11 bytes from the start.
• SEEK_SET is the reference point (beginning of the file).
• 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;
}
fclose(fp);
return 0;
}
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().
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;
}
// Rewind to Start
rewind(fp);
fclose(fp);
return 0;
}
Explanation:
Key Points:
Practice Exercise:
• Write a program to read and display the contents of a file twice using rewind().
• 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>
./program_name Alice
Output:
Hello, Alice!
Explanation:
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.
60
Topics Covered:
• 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.
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:
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.
• #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
#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:
#include <stdio.h>
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:
62
• Macros are replaced by their values before compilation (inline substitution).
Key Points:
Practice Exercise:
• Create macros for common mathematical operations (e.g., CUBE(x), MIN(a, b)).
Example:
#include <stdio.h>
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:
Key Points:
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;
}
Explanation:
• 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.
• 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.
Topics Covered:
65
10.2 Sorting Algorithms
Dynamic data structures allow flexible memory management and efficient manipulation of data.
• 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.
Structure of Node:
struct Node {
int data;
struct Node* next;
};
#include <stdio.h>
#include <stdlib.h>
// Node Structure
struct Node {
int data;
struct Node* next;
};
66
struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
return;
}
int main() {
struct Node* head = NULL;
insertEnd(&head, 10);
insertEnd(&head, 20);
insertEnd(&head, 30);
return 0;
}
Output:
Explanation:
Key Points:
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;
};
#include <stdio.h>
#include <stdlib.h>
// Node Structure
struct Node {
int data;
struct Node* next;
struct Node* prev;
};
if (*head == NULL) {
*head = newNode;
return;
}
int main() {
struct Node* head = NULL;
insertEnd(&head, 10);
insertEnd(&head, 20);
insertEnd(&head, 30);
68
displayList(head);
return 0;
}
Output:
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:
Practice Exercise:
• Implement a doubly linked list with functions to delete a node and reverse the list.
#include <stdio.h>
#include <stdlib.h>
// Node Structure
struct Node {
int data;
struct Node* next;
};
if (*head == NULL) {
*head = newNode;
newNode->next = *head;
return;
}
69
temp = temp->next;
}
temp->next = newNode;
}
int main() {
struct Node* head = NULL;
insertEnd(&head, 10);
insertEnd(&head, 20);
insertEnd(&head, 30);
displayList(head);
return 0;
}
Output:
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.
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).
• 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:
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.
• 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;
};
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:
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).
• 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:
Practice Exercise:
• Implement a queue with array-based functions to check if the queue is empty and reverse the queue.
• 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;
};
// 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);
// 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:
Practice Exercise:
• Implement a linked list-based queue with functions to find the maximum element in the queue.
• 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;
77
return (front == (rear + 1) % MAX);
}
// 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;
}
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:
Key Points:
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.
• 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;
};
#include <stdio.h>
79
#include <stdlib.h>
// Node Structure
struct Node {
int data;
struct Node* left;
struct Node* 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);
return 0;
}
Output:
Preorder Traversal: 1 2 4 5 3
Structure of Node:
struct Node {
80
int data;
struct Node* left;
struct Node* right;
};
#include <stdio.h>
#include <stdlib.h>
// Node Structure
struct Node {
int data;
struct Node* left;
struct Node* right;
};
return root;
}
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);
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:
Practice Exercise:
• 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).
• 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;
}
}
}
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n);
return 0;
}
Output:
Original Array: 64 34 25 12 22 11 90
Sorted Array: 11 12 22 25 34 64 90
Explanation:
Key Points:
Practice Exercise:
• Modify the Bubble Sort to stop early if no swaps occur in a pass (Optimized Bubble Sort).
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.
#include <stdio.h>
int main() {
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);
selectionSort(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:
Practice Exercise:
• Modify the Selection Sort to find the maximum element and sort in descending order.
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.
#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];
85
}
int main() {
int arr[] = {38, 27, 43, 3, 9, 82, 10};
int n = sizeof(arr) / sizeof(arr[0]);
mergeSort(arr, 0, n - 1);
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:
Practice Exercise:
86
• Modify the Merge Sort to sort in descending order.
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.
#include <stdio.h>
// Partition Function
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // Choosing the last element as pivot
int i = low - 1;
87
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
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:
Practice Exercise:
• 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)
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 30;
if (result == -1) {
printf("Element not found\n");
} else {
printf("Element found at index: %d\n", result);
}
return 0;
}
Output:
Key Points:
Practice Exercise:
• Modify the Linear Search to find all occurrences of the target value.
• 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:
• 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.
#include <stdio.h>
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:
Explanation:
Key Points:
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 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.
#include <stdio.h>
91
}
}
int main() {
int arr[] = {2, 4, 6, 8, 10, 12, 14};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 10;
if (result == -1) {
printf("Element not found\n");
} else {
printf("Element found at index: %d\n", result);
}
return 0;
}
Output:
Explanation:
Key Points:
Practice Exercise:
• Implement Recursive Binary Search to find the last occurrence of the target in a sorted array with
duplicate elements.
92
Practice Exercise:
• 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.
93