Menu

Unit 4: Programming in C (12 Hours)

Computer Science - Class 12

This chapter provides a comprehensive review of C programming, starting from core concepts like data types, operators, and I/O. It then delves into advanced topics such as functions (including recursion and storage classes), complex data structures like structures and unions, pointers, and essential file handling techniques.

Unit 4: Programming in C (12 Hours)

4.1 Review of C Programming Concepts

C programming is built upon fundamental elements that form the language's syntax and logic. Understanding these basics is crucial for writing effective C programs.

C Tokens, Keywords, Identifiers, Constants

  • C Tokens: These are the smallest individual units in a C program. They include keywords, identifiers, constants, strings, and special symbols.
  • Keywords: These are reserved words in C, each having a specific meaning and purpose. They cannot be used as identifiers. Examples: int, float, if, else, while, for, return, void.
  • Identifiers: These are names given to variables, functions, arrays, structures, etc., to uniquely identify them in a program. They must start with an alphabet or an underscore, followed by alphanumeric characters or underscores. C is case-sensitive, so myVar and myvar are different.
  • Constants: These are fixed values that do not change during program execution.
    • Integer Constants: Whole numbers (e.g., 10, -5, 0).
    • Floating-point Constants: Real numbers with a decimal point (e.g., 3.14, -0.001).
    • Character Constants: A single character enclosed in single quotes (e.g., 'A', '5', '\n').
    • String Constants: A sequence of characters enclosed in double quotes (e.g., "Hello, C!").

Data Types (int, float, char, double), sizeof operator

Data types specify the type of data a variable can hold. C provides a rich set of built-in data types:

  • int: Used to store integer values (whole numbers). Typically 2 or 4 bytes.
  • float: Used to store single-precision floating-point numbers. Typically 4 bytes.
  • double: Used to store double-precision floating-point numbers. Typically 8 bytes, offering more precision than float.
  • char: Used to store a single character. Typically 1 byte.

The sizeof operator is used to determine the size in bytes of a variable or data type.

Example: Data Types and sizeof operator

#include <stdio.h>

int main() {
    int a = 10;
    float b = 20.5;
    char c = 'X';
    double d = 30.75;

    printf("Size of int: %zu bytes\n", sizeof(a));
    printf("Size of float: %zu bytes\n", sizeof(b));
    printf("Size of char: %zu bytes\n", sizeof(c));
    printf("Size of double: %zu bytes\n", sizeof(d));
    printf("Size of int data type: %zu bytes\n", sizeof(int));
    return 0;
}

Output (may vary slightly based on compiler/system):

Size of int: 4 bytes
Size of float: 4 bytes
Size of char: 1 bytes
Size of double: 8 bytes
Size of int data type: 4 bytes

Input/Output: printf(), scanf(), getchar(), putchar()

C provides standard library functions for input and output operations, primarily found in the <stdio.h> header file.

  • printf(): Used to print formatted output to the console.
    Syntax: printf("format string", arg1, arg2, ...);
    Example: printf("Hello, %s! Your age is %d.\n", "Alice", 30);
  • scanf(): Used to read formatted input from the console.
    Syntax: scanf("format string", &var1, &var2, ...);
    Example: scanf("%d %f", &age, &salary); (Note the & for address-of operator)
  • getchar(): Reads a single character from the standard input. Returns an int, which is the ASCII value of the character.
    Syntax: char_variable = getchar();
  • putchar(): Writes a single character to the standard output.
    Syntax: putchar(char_variable);

Example: I/O Functions

#include <stdio.h>

int main() {
    int num;
    char ch;

    printf("Enter an integer: ");
    scanf("%d", &num);
    printf("You entered: %d\n", num);

    // Clear input buffer for getchar()
    while ((ch = getchar()) != '\n' && ch != EOF); 

    printf("Enter a character: ");
    ch = getchar();
    printf("You entered: ");
    putchar(ch);
    putchar('\n'); // Newline character

    return 0;
}

Output:

Enter an integer: 42
You entered: 42
Enter a character: K
You entered: K

Operators and Expressions, Type Casting

Operators are symbols that perform operations on variables and values. An expression is a combination of operators and operands.

  • Arithmetic Operators: +, -, *, /, % (modulus).
  • Relational Operators: == (equal to), != (not equal to), <, >, <=, >=.
  • Logical Operators: && (AND), || (OR), ! (NOT).
  • Assignment Operators: =, +=, -=, *=, /=, %=.
  • Increment/Decrement Operators: ++ (increment), -- (decrement).
  • Conditional (Ternary) Operator: condition ? expression1 : expression2;
  • Bitwise Operators: & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift).

Type Casting: It is the process of converting one data type to another.

  • Implicit Type Casting (Coercion): Done automatically by the compiler (e.g., int to float).
  • Explicit Type Casting: Done manually by the programmer using the cast operator (type).

Example: Operators and Type Casting

#include <stdio.h>

int main() {
    int x = 10, y = 3;
    float result_float;

    // Arithmetic
    printf("x + y = %d\n", x + y); // 13
    printf("x / y (integer division) = %d\n", x / y); // 3

    // Explicit Type Casting
    result_float = (float)x / y; // Cast x to float before division
    printf("x / y (float division) = %.2f\n", result_float); // 3.33

    // Relational
    printf("x > y is %d\n", x > y); // 1 (true)

    // Logical
    printf("(x > 5) && (y < 5) is %d\n", (x > 5) && (y < 5)); // 1 (true)

    // Increment
    printf("x before increment: %d\n", x); // 10
    x++;
    printf("x after increment: %d\n", x); // 11

    return 0;
}

Output:

x + y = 13
x / y (integer division) = 3
x / y (float division) = 3.33
x > y is 1
(x > 5) && (y < 5) is 1
x before increment: 10
x after increment: 11

4.2 Functions

Functions are self-contained blocks of code that perform a specific task. They are fundamental to modular programming in C.

4.2.1 Concept of Library and User-defined Functions

  • Library Functions: These are pre-defined functions available in C's standard library, ready for use. You just need to include the appropriate header file.
    • Examples: printf(), scanf() (from <stdio.h>), strlen() (string length from <string.h>), sqrt() (square root from <math.h>).
  • User-defined Functions: These are functions created by the programmer to perform specific tasks as per the program's requirements. They promote modularity and reusability.

Advantages of Functions:

  • Modularity: Breaking down a large program into smaller, manageable functions makes it easier to understand and maintain.
  • Reusability: A function can be called multiple times from different parts of the program, avoiding code duplication.
  • Readability: Programs become easier to read and understand when complex logic is encapsulated within functions with meaningful names.
  • Debugging: Isolating issues to specific functions simplifies the debugging process.

4.2.2 Function Definition, Prototype, Call, and Return

To use a user-defined function, you need to define it, declare its prototype, and then call it.

  • Function Definition: This is the actual implementation of the function, containing the code that performs its task.
    Syntax:
    return_type function_name(parameter_list) {
        // Function body (statements)
        // ...
        return value; // Optional, if return_type is not void
    }
  • Function Prototype (Declaration): This tells the compiler about the function's return type, name, and parameters before its actual definition or call. It's typically placed before the main() function.
    Syntax: return_type function_name(parameter_list);
    Example: int add(int a, int b);
  • Function Call: This is how you execute a function. You pass arguments (actual values) that correspond to the parameters in the function definition.
    Syntax: variable = function_name(arguments);
  • Return Statement: The return statement sends a value back from the function to the calling code. If a function does not return any value, its return type is void, and the return statement is optional (or return; can be used without a value).

Example: Function Definition, Prototype, Call, and Return

#include <stdio.h>

// Function Prototype
int multiply(int x, int y); 
void greet(char name[]); // void function

int main() {
    int num1 = 5, num2 = 10;
    int product;

    // Function Call
    product = multiply(num1, num2); 
    printf("The product is: %d\n", product);

    // Call to a void function
    greet("Alice");

    return 0;
}

// Function Definition
int multiply(int x, int y) {
    int result = x * y;
    return result; // Return statement
}

// Void function definition
void greet(char name[]) {
    printf("Hello, %s!\n", name);
    // No return statement needed for void, or simply return;
}

Output:

The product is: 50
Hello, Alice!

4.2.3 Accessing a Function by Passing Values (Call by Value)

In call by value, a copy of the actual argument's value is passed to the function's formal parameters. Any modifications made to the parameters inside the function do not affect the original variables in the calling function.

Example: Swap function with Call by Value (does NOT swap original)

#include <stdio.h>

void swapByValue(int a, int b) {
    int temp;
    temp = a;
    a = b;
    b = temp;
    printf("Inside swapByValue: a = %d, b = %d\n", a, b);
}

int main() {
    int x = 10, y = 20;
    printf("Before swapByValue: x = %d, y = %d\n", x, y);
    swapByValue(x, y); // Passing copies of x and y
    printf("After swapByValue: x = %d, y = %d\n", x, y); // x and y remain unchanged
    return 0;
}

Output:

Before swapByValue: x = 10, y = 20
Inside swapByValue: a = 20, b = 10
After swapByValue: x = 10, y = 20

As seen, x and y in main() remain 10 and 20, demonstrating that only copies were swapped within swapByValue().

4.2.4 Concept of Storage Classes

Storage classes in C determine the scope, lifetime, initial value, and storage location of variables.

  • automatic (auto):
    • Scope: Local to the block in which they are declared.
    • Lifetime: Created when the block is entered, destroyed when the block is exited.
    • Storage: Stack memory.
    • Default: All local variables are auto by default.
    • Initial Value: Garbage value (uninitialized).
  • static:
    • Scope: Local to the block (if declared inside a function) or file (if declared globally).
    • Lifetime: Persists throughout the program's execution. Retains its value between function calls.
    • Storage: Data segment.
    • Initial Value: Automatically initialized to zero if not explicitly initialized.

    Example: static variable

    #include <stdio.h>
    
    void func() {
        static int count = 0; // Initialized once
        count++;
        printf("Count: %d\n", count);
    }
    
    int main() {
        func(); // Count: 1
        func(); // Count: 2 (retains value)
        func(); // Count: 3
        return 0;
    }

    Output:

    Count: 1
    Count: 2
    Count: 3
  • extern:
    • Scope: Global, visible across multiple files.
    • Lifetime: Throughout the program's execution.
    • Storage: Data segment.
    • Use: Declares a global variable that is defined in another file or later in the same file. It tells the compiler that the variable exists elsewhere.
  • register:
    • Scope: Local to the block.
    • Lifetime: Created when the block is entered, destroyed when the block is exited.
    • Storage: CPU registers (if available and feasible).
    • Use: Suggests to the compiler that the variable should be stored in a CPU register for faster access. The compiler may ignore this suggestion if registers are unavailable or for optimization reasons.

4.2.5 Concept of Recursion

Recursion is a programming technique where a function calls itself directly or indirectly to solve a problem. A recursive function must have a base condition to stop the recursion and prevent infinite loops.

Example: Factorial Calculation using Recursion

The factorial of a non-negative integer n (denoted as n!) is the product of all positive integers less than or equal to n.
Formula: fact(n) = n * fact(n-1)
Base Case: fact(0) = 1

#include <stdio.h>

long int factorial(int n) {
    if (n == 0 || n == 1) { // Base condition
        return 1;
    } else {
        return n * factorial(n - 1); // Recursive call
    }
}

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

Output:

Factorial of 5 is 120

Example: Fibonacci Series using Recursion

The Fibonacci sequence is a series where the next number is the sum of the previous two numbers.
Formula: fib(n) = fib(n-1) + fib(n-2)
Base Cases: fib(0) = 0, fib(1) = 1

#include <stdio.h>

int fibonacci(int n) {
    if (n == 0) { // Base case 1
        return 0;
    } else if (n == 1) { // Base case 2
        return 1;
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2); // Recursive calls
    }
}

int main() {
    int i;
    printf("Fibonacci Series (first 10 terms):\n");
    for (i = 0; i < 10; i++) {
        printf("%d ", fibonacci(i));
    }
    printf("\n");
    return 0;
}

Output:

Fibonacci Series (first 10 terms):
0 1 1 2 3 5 8 13 21 34

Stack-based Execution, Memory Usage, When to use recursion vs iteration:

  • Stack-based Execution: Each recursive call adds a new frame to the call stack. This frame stores the function's local variables and return address. When a base case is reached, the functions start returning, and frames are popped off the stack.
  • Memory Usage: Recursion can consume more memory due to the overhead of maintaining the call stack. A deep recursion can lead to a "stack overflow" error.
  • When to use recursion:
    • When the problem has a naturally recursive structure (e.g., tree traversals, fractal generation).
    • When the recursive solution is more elegant and easier to understand than an iterative one.
  • When to use iteration:
    • When performance and memory efficiency are critical, as iteration generally uses less memory and is faster.
    • For simple problems that can be easily solved with loops.

4.3 Structures and Unions

Structures and unions are user-defined data types that allow you to group variables of different data types under a single name.

4.3.1 Structure Definition, Declaration, Initialization

A structure is a collection of variables (called members) of different data types, grouped under a single name. Each member occupies its own distinct memory location.

  • Structure Definition: Uses the struct keyword.
  • struct Student {
        char name[50];
        int age;
        float marks;
    }; // Don't forget the semicolon
  • Structure Declaration: Creating variables (objects) of the defined structure type.
  • struct Student s1; // Declares a variable s1 of type struct Student
    struct Student s2, s3; // Declares multiple variables
  • Structure Initialization: Assigning values to members during declaration.
  • struct Student s1 = {"Ram", 20, 85.5}; // Order matters
    struct Student s2 = {.name = "Sita", .age = 22, .marks = 92.0}; // Designated initializer (C99)

The sizeof() operator returns the total bytes occupied by all members of the structure, often including padding bytes for alignment.

Example: Structure Definition, Declaration, Initialization, and sizeof

#include <stdio.h>
#include <string.h> // For strcpy

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

int main() {
    // Declaration and Initialization
    struct Student s1 = {"Ram", 20, 85.5};

    // Declaration and then assignment
    struct Student s2;
    strcpy(s2.name, "Sita"); // String copy for character arrays
    s2.age = 22;
    s2.marks = 92.0;

    printf("Student 1: Name=%s, Age=%d, Marks=%.1f\n", s1.name, s1.age, s1.marks);
    printf("Student 2: Name=%s, Age=%d, Marks=%.1f\n", s2.name, s2.age, s2.marks);
    printf("Size of struct Student: %zu bytes\n", sizeof(struct Student));

    return 0;
}

Output (size may vary slightly):

Student 1: Name=Ram, Age=20, Marks=85.5
Student 2: Name=Sita, Age=22, Marks=92.0
Size of struct Student: 56 bytes

4.3.2 Accessing Member of Structure

Members of a structure are accessed using:

  • Dot operator (.): Used with a structure variable.
    Syntax: structure_variable.member_name
    Example: s1.name, s1.age
  • Arrow operator (->): Used with a pointer to a structure. It's a shorthand for dereferencing the pointer and then using the dot operator ((*ptr).member_name).
    Syntax: structure_pointer->member_name

Example: Accessing Members

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

struct Point {
    int x;
    int y;
};

int main() {
    struct Point p1;
    struct Point *ptr_p1;

    // Access using dot operator
    p1.x = 10;
    p1.y = 20;
    printf("Point p1: x = %d, y = %d\n", p1.x, p1.y);

    // Access using arrow operator with pointer
    ptr_p1 = &p1; // ptr_p1 now points to p1
    ptr_p1->x = 30; // Modifying x through pointer
    ptr_p1->y = 40; // Modifying y through pointer
    printf("Point p1 (modified): x = %d, y = %d\n", p1.x, p1.y); // Reflects changes
    printf("Point via ptr_p1: x = %d, y = %d\n", ptr_p1->x, ptr_p1->y);

    return 0;
}

Output:

Point p1: x = 10, y = 20
Point p1 (modified): x = 30, y = 40
Point via ptr_p1: x = 30, y = 40

4.3.3 Array of Structure

An array of structures allows you to store multiple records of the same structure type. Each element in the array is a structure variable.

Syntax: struct StructureName array_name[size];

Example: Array of Structure

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

struct Student {
    char name[50];
    int roll_no;
    float marks;
};

int main() {
    struct Student class[3]; // Array of 3 Student structures
    int i;

    // Initializing array of structures
    strcpy(class[0].name, "Alice");
    class[0].roll_no = 1;
    class[0].marks = 88.5;

    strcpy(class[1].name, "Bob");
    class[1].roll_no = 2;
    class[1].marks = 75.0;

    strcpy(class[2].name, "Charlie");
    class[2].roll_no = 3;
    class[2].marks = 91.2;

    printf("--- Student Records ---\n");
    for (i = 0; i < 3; i++) {
        printf("Roll No: %d, Name: %s, Marks: %.1f\n",
               class[i].roll_no, class[i].name, class[i].marks);
    }

    return 0;
}

Output:

--- Student Records ---
Roll No: 1, Name: Alice, Marks: 88.5
Roll No: 2, Name: Bob, Marks: 75.0
Roll No: 3, Name: Charlie, Marks: 91.2

4.3.4 Union Definition and Declaration

A union is similar to a structure, but all its members share the same memory location. The size of a union is equal to the size of its largest member. Only one member of a union can hold a value at any given time.

  • Union Definition: Uses the union keyword.
  • union Data {
        int i;
        float f;
        char str[20];
    };
  • Union Declaration: Creating variables of the defined union type.
  • union Data my_data;
  • Union Initialization: Only the first member can be initialized during declaration, or designated initializer can be used (C99).
  • union Data my_data = {10}; // Initializes 'i' to 10
    union Data another_data = {.f = 3.14}; // Initializes 'f' to 3.14

Example: Union Definition and Declaration

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

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;

    printf("Size of union Data: %zu bytes\n", sizeof(data)); // Will be size of str[20]

    data.i = 10;
    printf("data.i = %d\n", data.i); // i holds 10

    data.f = 220.5; // Now f holds 220.5, i's value is corrupted
    printf("data.f = %.1f\n", data.f);
    printf("data.i (corrupted) = %d\n", data.i); // Will print garbage or unexpected value

    strcpy(data.str, "C Programming"); // Now str holds the string, f and i are corrupted
    printf("data.str = %s\n", data.str);
    printf("data.i (corrupted) = %d\n", data.i); // Garbage
    printf("data.f (corrupted) = %.1f\n", data.f); // Garbage

    return 0;
}

Output (values for corrupted members will vary):

Size of union Data: 20 bytes
data.i = 10
data.f = 220.5
data.i (corrupted) = 1075838054
data.str = C Programming
data.i (corrupted) = 1852076867
data.f (corrupted) = 8.6

4.3.5 Difference Between Union and Structure

The key differences between structures and unions lie in how they manage memory and access members.

Feature Structure Union
Keyword struct union
Memory Allocation Allocates memory for all its members separately. The total size is the sum of all members' sizes (plus potential padding). Allocates memory for only the largest member. All members share this single memory location.
Member Access All members can hold values simultaneously and can be accessed independently. Only one member can hold a value at any given time. Assigning a value to one member overwrites the values of other members.
Use Cases When you need to store different types of related data together, where all data is relevant at the same time (e.g., student record, employee details). When you need to store different types of data in the same memory location, but only one type of data is relevant at any specific time, saving memory (e.g., variant types, hardware registers).
Initialization All members can be initialized during declaration. Only the first member (or a designated member in C99) can be initialized during declaration.

4.4 Pointers

Pointers are one of the most powerful features in C, allowing direct memory manipulation.

4.4.1 Definition of Pointer

A pointer is a variable that stores the memory address of another variable. Instead of holding a direct value, it holds the location where a value is stored.

  • Declaration: To declare a pointer, use the asterisk (*) symbol.
    Syntax: data_type *pointer_name;
    Example: int *ptr; (ptr is a pointer to an integer)
  • Address-of operator (&): This unary operator returns the memory address of a variable.
    Example: ptr = &var; (ptr now holds the address of var)
  • Dereference operator (*) (Indirection operator): This unary operator accesses the value stored at the memory address pointed to by the pointer.
    Example: value = *ptr; (value gets the content at the address stored in ptr)

Example: Pointer Definition

#include <stdio.h>

int main() {
    int num = 10;      // An integer variable
    int *ptr_num;      // A pointer to an integer

    ptr_num = &num;    // Store the address of num in ptr_num

    printf("Value of num: %d\n", num);         // Output: 10
    printf("Address of num: %p\n", (void*)&num); // Output: memory address of num
    printf("Value of ptr_num (address it stores): %p\n", (void*)ptr_num); // Output: same address as num
    printf("Value at the address ptr_num points to (*ptr_num): %d\n", *ptr_num); // Output: 10

    *ptr_num = 20; // Change the value of num through the pointer
    printf("New value of num: %d\n", num);     // Output: 20

    return 0;
}

Output (addresses will vary):

Value of num: 10
Address of num: 0x7ffe0e817a04
Value of ptr_num (address it stores): 0x7ffe0e817a04
Value at the address ptr_num points to (*ptr_num): 10
New value of num: 20

4.4.2 Address (&) and Indirection (*) Operator

  • Address-of operator (&): Used to get the memory address of a variable. For example, &var gives the starting address of var.
  • Indirection (Dereference) operator (*): Used to access the value stored at the address pointed to by a pointer. For example, *ptr gives the value at the address contained in ptr.

Pointer Arithmetic: Pointers can be incremented, decremented, added to, or subtracted from integers. This is particularly useful for traversing arrays.

  • ptr++: Increments the pointer to point to the next memory location of its data type.
  • ptr--: Decrements the pointer to point to the previous memory location.
  • ptr + n: Points to the n-th element after the current location.
  • ptr - n: Points to the n-th element before the current location.

Example: Pointer Arithmetic

#include <stdio.h>

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

    printf("First element: %d\n", *ptr); // 10
    printf("Address of first element: %p\n", (void*)ptr);

    ptr++; // Move to the next integer (arr[1])
    printf("Second element: %d\n", *ptr); // 20
    printf("Address of second element: %p\n", (void*)ptr);

    ptr = ptr + 2; // Move two integers forward (arr[3])
    printf("Fourth element: %d\n", *ptr); // 40
    printf("Address of fourth element: %p\n", (void*)ptr);

    return 0;
}

Output (addresses will vary, but will show increments of 4 bytes for int):

First element: 10
Address of first element: 0x7ffe0e8179e0
Second element: 20
Address of second element: 0x7ffe0e8179e4
Fourth element: 40
Address of fourth element: 0x7ffe0e8179ec

4.4.3 Pointer Expression and Assignment

  • Pointer Initialization: A pointer can be initialized during its declaration.
    Example: int x = 10; int *p = &x;
  • Assigning Pointer to another Pointer: One pointer can be assigned the value of another pointer, making both point to the same memory location.
    Example: int *p1 = &x; int *p2 = p1;
  • NULL Pointer: A pointer that does not point to any valid memory location. It is good practice to initialize pointers to NULL if they are not immediately assigned a valid address.
    Syntax: int *p = NULL; (NULL is defined in <stdio.h> or <stddef.h>)

Example: Pointer Assignment and NULL Pointer

#include <stdio.h>

int main() {
    int a = 100;
    int *ptr_a = &a; // ptr_a points to a

    int *ptr_b;
    ptr_b = ptr_a; // ptr_b now also points to a

    printf("Value of a: %d\n", a);
    printf("Value via ptr_a: %d\n", *ptr_a);
    printf("Value via ptr_b: %d\n", *ptr_b);

    *ptr_b = 200; // Change value through ptr_b
    printf("New value of a: %d\n", a); // a is now 200

    int *null_ptr = NULL;
    if (null_ptr == NULL) {
        printf("null_ptr is a NULL pointer.\n");
    }

    return 0;
}

Output:

Value of a: 100
Value via ptr_a: 100
Value via ptr_b: 100
New value of a: 200
null_ptr is a NULL pointer.

4.4.4 Call by Value and Call by Reference

These are two ways to pass arguments to functions, determining whether the original variables in the calling function can be modified.

  • Call by Value:
    • A copy of the argument's value is passed to the function.
    • Changes made to the parameters inside the function do not affect the original variables.
    • This was demonstrated in section 4.2.3 with the swapByValue example.
  • Call by Reference (using Pointers):
    • The memory address (pointer) of the argument is passed to the function.
    • The function can then use the dereference operator (*) to access and modify the original variable at that address.
    • This allows the function to directly change the values of variables in the calling function.

Example: Swap function with Call by Reference (using pointers)

#include <stdio.h>

void swapByReference(int *ptr_a, int *ptr_b) {
    int temp;
    temp = *ptr_a; // Get value at address ptr_a
    *ptr_a = *ptr_b; // Assign value at address ptr_b to address ptr_a
    *ptr_b = temp;   // Assign temp to address ptr_b
    printf("Inside swapByReference: *ptr_a = %d, *ptr_b = %d\n", *ptr_a, *ptr_b);
}

int main() {
    int x = 10, y = 20;
    printf("Before swapByReference: x = %d, y = %d\n", x, y);
    swapByReference(&x, &y); // Passing addresses of x and y
    printf("After swapByReference: x = %d, y = %d\n", x, y); // x and y are now swapped
    return 0;
}

Output:

Before swapByReference: x = 10, y = 20
Inside swapByReference: *ptr_a = 20, *ptr_b = 10
After swapByReference: x = 20, y = 10

Here, x and y in main() are successfully swapped because their memory addresses were passed, allowing the function to modify the original values.

4.5 Working with Files

File handling in C allows programs to store and retrieve data persistently from secondary storage (e.g., hard disk).

4.5.1 Concept of Data File

A data file is a collection of related data stored on a disk or other storage medium. Files provide a way to store data permanently, even after the program terminates.

  • Text Files:
    • Store data as a sequence of characters (ASCII values).
    • Human-readable when opened with a text editor.
    • Easier to debug but less efficient for numerical data.
  • Binary Files:
    • Store data in the same format as it is stored in memory.
    • Not human-readable (appears as gibberish in a text editor).
    • More efficient for storing numerical data and larger datasets.

4.5.2 Sequential and Random File

  • Sequential File Access:
    • Data is accessed in a linear fashion, from the beginning to the end.
    • To read the 10th record, you must first read the preceding 9 records.
    • Suitable for logs, reports, or when data is processed in order.
  • Random File Access:
    • Allows direct access to any record in the file without reading preceding records.
    • Achieved using functions like fseek(), ftell(), and rewind().
    • Suitable for databases or applications where specific records need to be quickly retrieved or updated.

4.5.3 File Manipulation Functions

C provides several functions for reading from and writing to files, typically found in <stdio.h>.

  • putw(int w, FILE *fp): Writes an integer w to the file pointed to by fp.
  • getw(FILE *fp): Reads an integer from the file pointed to by fp.
  • putc(int char_val, FILE *fp): Writes a character char_val to the file pointed to by fp.
  • getc(FILE *fp): Reads a character from the file pointed to by fp.
  • fprintf(FILE *fp, const char *format, ...): Writes formatted output to the file pointed to by fp (similar to printf).
  • fscanf(FILE *fp, const char *format, ...): Reads formatted input from the file pointed to by fp (similar to scanf).
  • fputs(const char *s, FILE *fp): Writes a string s to the file pointed to by fp.
  • fgets(char *s, int n, FILE *fp): Reads a string of at most n-1 characters from the file pointed to by fp into buffer s.

4.5.4 Opening, Reading, Writing, Appending

File operations involve opening a file, performing I/O, and then closing it.

  • fopen(const char *filename, const char *mode): Opens a file and returns a file pointer (FILE *). If it fails, it returns NULL.
    • Modes:
      • "r": Read mode (file must exist).
      • "w": Write mode (creates a new file or truncates existing one).
      • "a": Append mode (creates if not exists, writes to end if exists).
      • "rb", "wb", "ab": Binary modes for read, write, append respectively.
      • "r+", "w+", "a+": Read and write modes.
  • fclose(FILE *fp): Closes the file pointed to by fp, releasing resources. Returns 0 on success, EOF on error.
  • feof(FILE *fp): Checks for end-of-file indicator on the stream pointed to by fp. Returns non-zero if EOF, 0 otherwise.

Complete File I/O Program Example: Writing to and Reading from a Text File

#include <stdio.h>
#include <stdlib.h> // For exit()

int main() {
    FILE *fp;
    char text_to_write[] = "Hello, C File Handling!\nThis is a test line.\n";
    char buffer[100];
    int num_to_write = 123;
    int num_read;

    // --- Writing to a file (using "w" mode) ---
    fp = fopen("example.txt", "w");
    if (fp == NULL) {
        perror("Error opening file for writing");
        exit(EXIT_FAILURE);
    }

    fprintf(fp, "%s", text_to_write); // Write a string
    fprintf(fp, "Integer value: %d\n", num_to_write); // Write an integer
    fputs("Another line using fputs.\n", fp); // Write another string

    fclose(fp);
    printf("Data written to example.txt successfully.\n");

    // --- Appending to a file (using "a" mode) ---
    fp = fopen("example.txt", "a");
    if (fp == NULL) {
        perror("Error opening file for appending");
        exit(EXIT_FAILURE);
    }
    fprintf(fp, "This line was appended.\n");
    fclose(fp);
    printf("Data appended to example.txt successfully.\n");

    // --- Reading from a file (using "r" mode) ---
    fp = fopen("example.txt", "r");
    if (fp == NULL) {
        perror("Error opening file for reading");
        exit(EXIT_FAILURE);
    }

    printf("\n--- Content of example.txt ---\n");
    // Read line by line using fgets
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }

    // Rewind to the beginning to read integer specifically
    rewind(fp); // Moves file pointer to the beginning
    // Seek for the line with "Integer value:" and read the number
    char line[100];
    while (fgets(line, sizeof(line), fp) != NULL) {
        if (sscanf(line, "Integer value: %d", &num_read) == 1) {
            printf("Found integer in file: %d\n", num_read);
            break;
        }
    }

    fclose(fp);
    printf("File read operations completed.\n");

    return 0;
}

Output (console):

Data written to example.txt successfully.
Data appended to example.txt successfully.

--- Content of example.txt ---
Hello, C File Handling!
This is a test line.
Integer value: 123
Another line using fputs.
This line was appended.
Found integer in file: 123
File read operations completed.

After running this program, a file named example.txt will be created/updated in the same directory with the following content:

Hello, C File Handling!
This is a test line.
Integer value: 123
Another line using fputs.
This line was appended.