Menu

3. Programming Language and Its Applications (ACtE03)

Computer Engineering (Nepal Engineering Council) - Engineering Licence Exam

This chapter offers a deep dive into C and C++ programming, commencing with C's fundamental building blocks, control structures, functions, arrays, and file operations. It then systematically transitions to C++'s advanced object-oriented features, including classes, inheritance, polymorphism, templates, and robust exception handling for comprehensive software development.

3. Programming Language and Its Applications (ACtE03)

3.1 Introduction to C Programming

C is a powerful, general-purpose programming language developed by Dennis Ritchie at Bell Labs. It is known for its efficiency, low-level memory access, and portability, making it ideal for system programming and embedded systems.

C Tokens

C tokens are the smallest individual units in a C program. They are categorized into six types:

  • Keywords: Reserved words with predefined meanings (e.g., int, if, while, for, return). There are 32 keywords in ANSI C.
  • Identifiers: User-defined names given to variables, functions, arrays, etc. They must start with an alphabet or underscore, followed by alphabets, digits, or underscores. Case-sensitive (e.g., myVariable, _count, addNumbers).
  • Constants: Fixed values that do not change during program execution (e.g., 10 (integer), 3.14 (float), 'A' (character), "Hello" (string)).
  • Strings: A sequence of characters enclosed in double quotes (e.g., "C Programming"). Strings are terminated by a null character (\0).
  • Operators: Symbols that perform operations on operands (e.g., +, -, *, /, =, ==).
  • Special Symbols: Characters with specific meanings (e.g., {} (block), () (function/expression), [] (array), ; (statement terminator), # (preprocessor directive)).

Operators with Precedence Table

Operators are symbols that tell the compiler to perform specific mathematical or logical manipulations. C supports a rich set of operators:

  • Arithmetic Operators: + (addition), - (subtraction), * (multiplication), / (division), % (modulo).
    int a = 10, b = 3;
    int sum = a + b; // 13
    int diff = a - b; // 7
    int prod = a * b; // 30
    int quot = a / b; // 3
    int rem = a % b;  // 1
  • Relational Operators: Used for comparison; return 1 (true) or 0 (false). == (equal to), != (not equal to), > (greater than), < (less than), >= (greater than or equal to), <= (less than or equal to).
    int x = 5, y = 10;
    int isEqual = (x == y); // 0 (false)
    int isGreater = (y > x); // 1 (true)
  • Logical Operators: Combine or negate relational expressions. && (logical AND), || (logical OR), ! (logical NOT).
    int age = 20, hasLicense = 1;
    int canDrive = (age >= 18 && hasLicense); // 1 (true)
    int canVote = (age >= 18 || hasLicense); // 1 (true)
    int notTrue = !(age < 18); // 1 (true)
  • Bitwise Operators: Operate on individual bits. & (bitwise AND), | (bitwise OR), ^ (bitwise XOR), ~ (bitwise NOT), << (left shift), >> (right shift).
    int p = 5; // 0101
    int q = 3; // 0011
    int and_res = p & q; // 0001 (1)
    int or_res = p | q;  // 0111 (7)
    int xor_res = p ^ q; // 0110 (6)
    int not_res = ~p;    // 1010 (-6, for 8-bit signed)
    int left_shift = p << 1; // 1010 (10)
  • Assignment Operators: Assign values to variables. = (simple assignment), +=, -=, *=, /=, %= (compound assignment).
    int num = 10;
    num += 5; // num is now 15 (num = num + 5)
  • Ternary Operator (Conditional Operator): condition ? expression1 : expression2; If condition is true, expression1 is evaluated; otherwise, expression2.
    int marks = 75;
    char grade = (marks >= 60) ? 'A' : 'B'; // grade is 'A'

Operator Precedence (Simplified)

Category Operators Associativity
Postfix () [] . -> ++ -- Left-to-right
Unary + - ! ~ ++ -- (type)* & sizeof Right-to-left
Multiplicative * / % Left-to-right
Additive + - Left-to-right
Shift << >> Left-to-right
Relational < <= > >= Left-to-right
Equality == != Left-to-right
Bitwise AND & Left-to-right
Bitwise XOR ^ Left-to-right
Bitwise OR | Left-to-right
Logical AND && Left-to-right
Logical OR || Left-to-right
Conditional ? : Right-to-left
Assignment = += -= *= /= %= &= ^= |= <<= >>= Right-to-left
Comma , Left-to-right

Formatted Input/Output (printf, scanf)

Formatted I/O functions allow reading and writing data in a specific format using format specifiers.

  • printf(): Used to print formatted output to the console.
    #include <stdio.h>
    int main() {
        int age = 25;
        float height = 1.75;
        char initial = 'J';
        printf("Age: %d, Height: %.2f, Initial: %c\n", age, height, initial);
        return 0;
    }
    // Output: Age: 25, Height: 1.75, Initial: J

    Common format specifiers: %d (integer), %f (float), %lf (double), %c (character), %s (string), %x (hexadecimal), %o (octal).

  • scanf(): Used to read formatted input from the console.
    #include <stdio.h>
    int main() {
        int num;
        float price;
        printf("Enter an integer and a float: ");
        scanf("%d %f", &num, &price);
        printf("You entered: %d and %.2f\n", num, price);
        return 0;
    }

    Note the & (address-of) operator before variable names in scanf().

Unformatted Input/Output (getchar, putchar, gets, puts)

These functions handle single characters or strings without any formatting options.

  • getchar(): Reads a single character from the standard input.
    #include <stdio.h>
    int main() {
        char ch;
        printf("Enter a character: ");
        ch = getchar();
        printf("You entered: ");
        putchar(ch);
        printf("\n");
        return 0;
    }
  • putchar(): Writes a single character to the standard output. (See example above).
  • gets(): Reads a string from the standard input until a newline character is encountered. (Potentially unsafe, prefer fgets())
    #include <stdio.h>
    int main() {
        char name[50];
        printf("Enter your name: ");
        gets(name); // Unsafe: no buffer overflow check
        printf("Hello, ");
        puts(name);
        return 0;
    }
  • puts(): Writes a string to the standard output, automatically adding a newline character at the end. (See example above).

Control Statements

Control statements dictate the flow of execution in a program.

  • if: Executes a block of code if a condition is true.
    if (condition) {
        // code to execute if condition is true
    }
  • if-else: Executes one block if true, another if false.
    if (condition) {
        // code if true
    } else {
        // code if false
    }
  • nested if-else: An if-else statement inside another if or else block.
    if (condition1) {
        if (condition2) {
            // code if condition1 and condition2 are true
        } else {
            // code if condition1 is true, but condition2 is false
        }
    } else {
        // code if condition1 is false
    }
  • switch-case: Used for multi-way branching based on the value of an expression.
    switch (expression) {
        case value1:
            // code for value1
            break;
        case value2:
            // code for value2
            break;
        default:
            // code if no match
    }

Looping

Loops allow repeated execution of a block of code.

  • for loop: Used when the number of iterations is known.
    for (initialization; condition; update) {
        // code to repeat
    }
    Example:
    for (int i = 0; i < 5; i++) {
        printf("%d ", i); // Output: 0 1 2 3 4
    }
  • while loop: Executes as long as a condition is true.
    while (condition) {
        // code to repeat
    }
    Example:
    int i = 0;
    while (i < 5) {
        printf("%d ", i);
        i++;
    } // Output: 0 1 2 3 4
  • do-while loop: Executes the block at least once, then checks the condition.
    do {
        // code to repeat
    } while (condition);
    Example:
    int i = 0;
    do {
        printf("%d ", i);
        i++;
    } while (i < 5); // Output: 0 1 2 3 4
  • nested loops: A loop inside another loop.
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("(%d,%d) ", i, j);
        }
    }
    // Output: (0,0) (0,1) (0,2) (1,0) (1,1) (1,2)
  • break: Terminates the innermost loop or switch statement.
    for (int i = 0; i < 10; i++) {
        if (i == 5) break;
        printf("%d ", i); // Output: 0 1 2 3 4
    }
  • continue: Skips the rest of the current iteration and proceeds to the next iteration.
    for (int i = 0; i < 5; i++) {
        if (i == 2) continue;
        printf("%d ", i); // Output: 0 1 3 4
    }
  • goto: Transfers control to a labeled statement. Generally discouraged due to making code hard to read and maintain.
    // Example of goto (generally avoid)
    int i = 0;
    loop_start:
    if (i < 5) {
        printf("%d ", i);
        i++;
        goto loop_start;
    } // Output: 0 1 2 3 4

User-defined Functions

Functions are blocks of code that perform a specific task, promoting modularity and reusability.

  • Declaration (Prototype): Tells the compiler about the function's name, return type, and parameters.
    return_type function_name(parameter_list);
    Example: int add(int a, int b);
  • Definition: Contains the actual code of the function.
    int add(int a, int b) {
        return a + b;
    }
  • Call: Invokes the function to execute its code.
    int result = add(10, 20);
  • Parameters: Values passed to the function during a call.
  • Return Type: The type of value the function sends back to the caller. void if no value is returned.

Recursive Functions

A function that calls itself is called a recursive function. It must have a base case to stop the recursion.

  • Factorial Example:
    long long factorial(int n) {
        if (n == 0 || n == 1) {
            return 1; // Base case
        } else {
            return n * factorial(n - 1); // Recursive call
        }
    }
  • Fibonacci Example:
    int fibonacci(int n) {
        if (n <= 1) {
            return n; // Base case
        } else {
            return fibonacci(n - 1) + fibonacci(n - 2); // Recursive call
        }
    }

Arrays

An array is a collection of elements of the same data type stored at contiguous memory locations.

  • 1-D Array:
    // Declaration
    int numbers[5];
    // Initialization
    int scores[] = {10, 20, 30, 40, 50};
    // Traversal
    for (int i = 0; i < 5; i++) {
        printf("%d ", scores[i]);
    }
  • 2-D Array (Matrix):
    // Declaration and Initialization
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    // Traversal
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
  • Multi-dimensional Array: Arrays with more than two dimensions (e.g., int cube[2][2][2];).

String Manipulations

C strings are character arrays terminated by a null character \0. The <string.h> header provides functions for string manipulation.

  • strlen(str): Returns the length of the string (excluding \0).
    char s1[] = "Hello";
    int len = strlen(s1); // len is 5
  • strcpy(dest, src): Copies the content of src string to dest string.
    char s2[20];
    strcpy(s2, s1); // s2 is now "Hello"
  • strcat(dest, src): Concatenates (joins) src string to the end of dest string.
    char s3[30] = "World";
    strcat(s1, s3); // s1 is now "HelloWorld"
  • strcmp(str1, str2): Compares two strings lexicographically. Returns 0 if equal, <0 if str1 is less than str2, >0 if str1 is greater than str2.
    int cmp = strcmp("apple", "banana"); // cmp is <0
  • strupr(str): Converts all lowercase letters in a string to uppercase. (Non-standard, often available in compilers like GCC for Windows).
    char s4[] = "hello";
    strupr(s4); // s4 is now "HELLO"
  • strlwr(str): Converts all uppercase letters in a string to lowercase. (Non-standard).
    char s5[] = "WORLD";
    strlwr(s5); // s5 is now "world"

3.2 Pointers, Structure and Data Files in C

Pointer Basics

A pointer is a variable that stores the memory address of another variable.

  • Declaration: data_type *pointer_name;
    int *ptr; // ptr is a pointer to an integer
  • Initialization: Assigning the address of a variable to a pointer using the address-of operator (&).
    int var = 10;
    ptr = &var; // ptr now holds the address of var
  • Dereferencing: Accessing the value stored at the memory address pointed to by the pointer using the dereference operator (*).
    printf("Value of var: %d\n", *ptr); // Output: 10
    *ptr = 20; // var is now 20

Pointer Arithmetic

Pointers can be incremented, decremented, added to, or subtracted from, but only with integers. The arithmetic operations are scaled by the size of the data type the pointer points to.

  • Increment/Decrement: Moves the pointer to the next/previous memory location of its data type.
    int arr[] = {10, 20, 30};
    int *p = arr; // p points to arr[0] (value 10)
    p++;          // p now points to arr[1] (value 20)
    printf("%d\n", *p); // Output: 20
  • Addition/Subtraction: Adding an integer n to a pointer moves it n * sizeof(data_type) bytes forward.
    p = p + 1; // Same as p++
  • Subtraction of two pointers: Returns the number of elements between them (only valid for pointers to elements of the same array).
    int *p1 = &arr[0];
    int *p2 = &arr[2];
    int diff = p2 - p1; // diff is 2

Pointer and Array

Array names are essentially constant pointers to their first element.

  • Pointer Arithmetic with Arrays:
    int arr[] = {10, 20, 30};
    int *p = arr; // p points to arr[0]
    printf("%d\n", *(p + 1)); // Accesses arr[1] (value 20)
    printf("%d\n", arr[2]);    // Accesses arr[2] (value 30)
    printf("%d\n", *(arr + 2)); // Same as arr[2]
  • Array of Pointers: An array where each element is a pointer.
    int a = 10, b = 20, c = 30;
    int *ptr_array[3]; // Array of 3 integer pointers
    ptr_array[0] = &a;
    ptr_array[1] = &b;
    ptr_array[2] = &c;
    printf("%d\n", *ptr_array[1]); // Output: 20

Passing Pointer to Function (Call by Reference)

Instead of passing values, we pass addresses, allowing the function to modify the original variables.

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 5, b = 10;
    swap(&a, &b); // Pass addresses
    printf("a = %d, b = %d\n", a, b); // Output: a = 10, b = 5
    return 0;
}

Structure vs Union

Both are user-defined data types to group different types of data, but they differ in memory allocation.

  • Structure:
    • Syntax: struct StructureName { data_type member1; ... };
    • Memory Allocation: Each member gets its own separate memory space. Total size is the sum of sizes of all members (plus padding).
    • Access: All members can be accessed simultaneously.
    • Example:
      struct Student {
          char name[50];
          int roll;
          float marks;
      };
      struct Student s1; // Allocates memory for name, roll, and marks
  • Union:
    • Syntax: union UnionName { data_type member1; ... };
    • Memory Allocation: All members share the same memory location. The size of a union is equal to the size of its largest member.
    • Access: Only one member can be accessed at a time; changing one member's value overwrites others.
    • Example:
      union Data {
          int i;
          float f;
          char str[20];
      };
      union Data d1; // Allocates memory equal to sizeof(str)

Array of Structures

An array where each element is a structure.

struct Point {
    int x, y;
};

int main() {
    struct Point points[3]; // Array of 3 Point structures
    points[0].x = 10;
    points[0].y = 20;
    // ... initialize others
    printf("Point 0: (%d, %d)\n", points[0].x, points[0].y);
    return 0;
}

Passing Structure to Function

Structures can be passed by value or by address (pointer).

  • By Value: Creates a copy; original structure remains unchanged.
    void displayPoint(struct Point p) {
        printf("X: %d, Y: %d\n", p.x, p.y);
    }
    // Call: displayPoint(myPoint);
  • By Reference (Pointer): Passes the address; allows modification of the original.
    void modifyPoint(struct Point *p) {
        p->x = 100; // Use -> operator for pointer to structure
    }
    // Call: modifyPoint(&myPoint);

Structure and Pointer

A pointer can point to a structure. Members are accessed using the arrow (->) operator.

struct Person {
    char name[50];
    int age;
};

int main() {
    struct Person p1 = {"Alice", 30};
    struct Person *ptr_p1;
    ptr_p1 = &p1;

    printf("Name: %s, Age: %d\n", ptr_p1->name, ptr_p1->age);
    // Equivalent to (*ptr_p1).name, (*ptr_p1).age
    return 0;
}

Input/Output Operations on Files

C provides functions to perform file operations using the <stdio.h> header.

  • FILE *fopen(const char *filename, const char *mode): Opens a file. Returns a FILE pointer or NULL on failure.

    Modes: "r" (read), "w" (write, creates/truncates), "a" (append), "rb", "wb", "ab" (binary modes), "r+" (read/write), etc.

  • int fclose(FILE *stream): Closes an opened file. Returns 0 on success.
  • int fprintf(FILE *stream, const char *format, ...): Writes formatted output to a file.
  • int fscanf(FILE *stream, const char *format, ...): Reads formatted input from a file.
  • char *fgets(char *str, int n, FILE *stream): Reads a line from a file (up to n-1 characters or until newline). Safer than gets().
  • int fputs(const char *str, FILE *stream): Writes a string to a file.
#include <stdio.h>
int main() {
    FILE *fp;
    fp = fopen("example.txt", "w"); // Open for writing
    if (fp == NULL) {
        printf("Error opening file!\n");
        return 1;
    }
    fprintf(fp, "Hello, File!\n");
    fputs("This is another line.\n", fp);
    fclose(fp);

    fp = fopen("example.txt", "r"); // Open for reading
    if (fp == NULL) {
        printf("Error opening file!\n");
        return 1;
    }
    char buffer[100];
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }
    fclose(fp);
    return 0;
}

Sequential and Random Access to File

  • Sequential Access: Reading/writing data from the beginning of the file to the end. (Done by default with fprintf, fscanf, fgets, fputs).
  • Random Access: Moving the file pointer to a specific location to read/write data.
    • int fseek(FILE *stream, long offset, int origin): Sets the file position indicator.
      • offset: Number of bytes to move.
      • origin: SEEK_SET (from beginning), SEEK_CUR (from current position), SEEK_END (from end).
    • long ftell(FILE *stream): Returns the current position of the file pointer.
    • void rewind(FILE *stream): Sets the file pointer to the beginning of the file (equivalent to fseek(stream, 0, SEEK_SET)).
    #include <stdio.h>
    int main() {
        FILE *fp = fopen("random.txt", "w+"); // Read and write
        fputs("ABCDEFGHIJ", fp); // Write 10 characters
        fseek(fp, 3, SEEK_SET); // Move to 4th character ('D')
        fputc('X', fp); // Overwrite 'D' with 'X'
        fseek(fp, 0, SEEK_END); // Move to end
        long size = ftell(fp); // Get file size
        printf("File size: %ld\n", size); // Output: 10
        rewind(fp); // Go to beginning
        char ch = fgetc(fp); // Read 'A'
        printf("First char: %c\n", ch);
        fclose(fp);
        return 0;
    }
    // File content after execution: ABCXEFGHIJ

3.3 C++ Language Constructs with Objects and Classes

C++ is an extension of C, adding object-oriented programming (OOP) features. It supports both procedural and object-oriented paradigms.

Namespace

Namespaces are used to organize code into logical groups and prevent name collisions, especially in large projects or when using multiple libraries.

  • Declaration:
    namespace MySpace {
        int value = 10;
        void func() { /* ... */ }
    }
  • using directive: Brings all names from a namespace into the current scope.
    using namespace MySpace;
    // Now you can directly use value and func()
    int x = value;
  • using declaration: Brings a specific name from a namespace into the current scope.
    using MySpace::value;
    // Now you can use 'value' directly, but func() still needs MySpace::func()
    int y = value;

Function Overloading

Allows multiple functions with the same name but different parameter lists (number, type, or order of parameters) within the same scope. The compiler decides which function to call based on the arguments provided.

  • Examples:
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
    int add(int a, int b, int c) { return a + b + c; }
    
    int main() {
        add(5, 10);      // Calls int add(int, int)
        add(5.5, 10.5);  // Calls double add(double, double)
        add(1, 2, 3);    // Calls int add(int, int, int)
        return 0;
    }
  • Rules:
    • Only parameter list (number, type, order) can differentiate overloaded functions.
    • Return type alone is not sufficient for overloading.
    • Default arguments can sometimes lead to ambiguity.

Inline Functions

A hint to the compiler to replace the function call with the function's body at compile time, potentially improving performance by avoiding function call overhead, especially for small, frequently called functions.

inline int multiply(int a, int b) {
    return a * b;
}

int main() {
    int result = multiply(5, 3); // Compiler might replace this with '5 * 3'
    return 0;
}

Default Argument

Allows a function parameter to have a default value if no argument is provided for that parameter during a function call.

void display(int a, int b = 0, int c = 0) {
    std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}

int main() {
    display(10);        // a=10, b=0, c=0
    display(10, 20);    // a=10, b=20, c=0
    display(10, 20, 30); // a=10, b=20, c=30
    return 0;
}

Default arguments must be specified from right to left.

Pass/Return by Reference

  • Pass by Reference: Allows a function to modify the original variable passed as an argument by passing its reference.
    void increment(int &val) { // val is a reference to an int
        val++;
    }
    
    int main() {
        int num = 10;
        increment(num); // num becomes 11
        return 0;
    }
  • Return by Reference: A function can return a reference to a variable, allowing the caller to modify the original variable. Care must be taken not to return a reference to a local variable that goes out of scope.
    int global_var = 5;
    int &getGlobalVar() {
        return global_var;
    }
    
    int main() {
        getGlobalVar() = 10; // Modifies global_var
        std::cout << global_var << std::endl; // Output: 10
        return 0;
    }

Introduction to Class and Object

C++ is an object-oriented language, and classes and objects are its core concepts.

  • Class: A blueprint or a template for creating objects. It encapsulates data (member variables) and functions (member functions) that operate on that data.
    class Car {
    public:
        // Member variables (attributes)
        std::string brand;
        int year;
    
        // Member function (behavior)
        void displayInfo() {
            std::cout << "Brand: " << brand << ", Year: " << year << std::endl;
        }
    };
  • Object: An instance of a class. When a class is defined, no memory is allocated; memory is allocated only when an object is created.
    int main() {
        Car myCar; // myCar is an object of the Car class
        myCar.brand = "Toyota";
        myCar.year = 2020;
        myCar.displayInfo();
        return 0;
    }

Access Specifiers

Keywords that control the visibility and accessibility of class members.

  • public: Members are accessible from anywhere, inside or outside the class.
  • private: Members are accessible only from within the same class. (Default for classes).
  • protected: Members are accessible within the same class and by derived classes (inheritance).

Objects and Member Access

  • Dot Operator (.): Used to access members of an object directly.
    Car myCar;
    myCar.brand = "Honda";
    myCar.displayInfo();
  • Arrow Operator (->): Used to access members of an object through a pointer to that object.
    Car *ptrCar = &myCar;
    ptrCar->brand = "Nissan";
    ptrCar->displayInfo(); // Equivalent to (*ptrCar).displayInfo()

Defining Member Function

  • Inside Class: Functions defined inside the class body are implicitly inline.
    class Dog {
    public:
        void bark() {
            std::cout << "Woof!" << std::endl;
        }
    };
  • Outside Class with Scope Resolution (::): Preferred for larger functions to keep class definition clean.
    class Cat {
    public:
        void meow();
    };
    
    void Cat::meow() { // Definition outside class using scope resolution
        std::cout << "Meow!" << std::endl;
    }

Constructor and its types

Special member functions automatically called when an object is created. They initialize the object's members. They have the same name as the class and no return type.

  • Default Constructor: A constructor that takes no arguments. If not provided, C++ generates a default one.
    class Box {
        int length, width;
    public:
        Box() { // Default constructor
            length = 0;
            width = 0;
            std::cout << "Default constructor called." << std::endl;
        }
    };
    Box b1; // Calls default constructor
  • Parameterized Constructor: Takes arguments to initialize object members with specific values.
    class Box {
        int length, width;
    public:
        Box(int l, int w) { // Parameterized constructor
            length = l;
            width = w;
            std::cout << "Parameterized constructor called." << std::endl;
        }
    };
    Box b2(10, 5); // Calls parameterized constructor
  • Copy Constructor: Initializes an object by copying an existing object of the same class.
    class Box {
        int length, width;
    public:
        Box(const Box &other) { // Copy constructor
            length = other.length;
            width = other.width;
            std::cout << "Copy constructor called." << std::endl;
        }
    };
    Box b3 = b2; // Calls copy constructor (initialization)
    Box b4(b2);  // Also calls copy constructor

Destructor

A special member function automatically called when an object is destroyed (goes out of scope, or delete is called for dynamically allocated objects). Used to release resources (e.g., dynamically allocated memory). Has the same name as the class prefixed with a tilde (~) and takes no arguments.

class MyClass {
public:
    MyClass() { std::cout << "Constructor called." << std::endl; }
    ~MyClass() { std::cout << "Destructor called." << std::endl; }
};

int main() {
    MyClass obj; // Constructor called
    // obj goes out of scope, destructor called
    return 0;
}

Dynamic Memory Allocation for Objects and Object Array (new, delete)

C++ uses new and delete operators for dynamic memory management, which are type-safe and automatically call constructors/destructors.

  • Single Object:
    MyClass *ptr = new MyClass(); // Allocates MyClass object, calls constructor
    // ... use ptr ...
    delete ptr; // Deallocates memory, calls destructor
  • Object Array:
    MyClass *arr = new MyClass[5]; // Allocates array of 5 MyClass objects, calls default constructor for each
    // ... use arr ...
    delete[] arr; // Deallocates array memory, calls destructor for each element

this Pointer

A special pointer that points to the current object on which a member function is called. It is implicitly passed to all non-static member functions.

class Point {
    int x, y;
public:
    void setCoords(int x, int y) {
        this->x = x; // 'this->x' refers to the member variable
        this->y = y; // 'x' and 'y' refer to function parameters
    }
    Point &compare(Point &other) { // Returns reference to the object with larger x
        return (this->x > other.x) ? *this : other;
    }
};

Static Data Member and Static Function

  • Static Data Member: A member variable declared with the static keyword. It belongs to the class, not to any specific object. All objects of the class share a single copy of the static data member. It must be defined outside the class.
    class Counter {
    public:
        static int count; // Declaration
        Counter() { count++; }
    };
    int Counter::count = 0; // Definition and initialization outside class
    
    int main() {
        Counter c1, c2, c3;
        std::cout << Counter::count << std::endl; // Output: 3
        return 0;
    }
  • Static Function: A member function declared with static. It can only access static data members and static member functions of the class. It can be called using the class name without creating an object.
    class MyMath {
    public:
        static int add(int a, int b) {
            return a + b;
        }
    };
    // Call without an object
    int sum = MyMath::add(5, 7); // sum is 12

Constant Member Functions and Constant Objects

  • Constant Object: An object declared with const. Its data members cannot be modified after initialization. Only const member functions can be called on a const object.
    const MyClass obj_const;
  • Constant Member Function: A member function declared with const after its parameter list. It guarantees that the function will not modify any data members of the object.
    class Immutable {
        int value;
    public:
        Immutable(int v) : value(v) {}
        int getValue() const { // Constant member function
            return value;
        }
        // void setValue(int v) const { value = v; } // ERROR: cannot modify in const function
    };
    
    int main() {
        const Immutable i1(10);
        std::cout << i1.getValue() << std::endl; // OK
        // i1.setValue(20); // ERROR
        return 0;
    }

Friend Function and Friend Classes

  • Friend Function: A non-member function that is granted special permission to access the private and protected members of a class. Declared inside the class with the friend keyword.
    class MyData {
        int private_val;
    public:
        MyData(int v) : private_val(v) {}
        friend void showPrivate(MyData obj); // Friend function declaration
    };
    
    void showPrivate(MyData obj) {
        std::cout << "Private value: " << obj.private_val << std::endl;
    }
  • Friend Class: If a class is declared as a friend of another class, all member functions of the friend class can access the private and protected members of the class that granted friendship.
    class A {
        int x;
        friend class B; // B is a friend of A
    public:
        A(int val) : x(val) {}
    };
    
    class B {
    public:
        void displayA(A obj) {
            std::cout << "A's private x: " << obj.x << std::endl;
        }
    };

3.4 Features of Object-Oriented Programming

OOP is a programming paradigm based on the concept of "objects", which can contain data and code. Key features include Encapsulation, Abstraction, Inheritance, and Polymorphism.

Operator Overloading

Allows operators to be redefined or overloaded for user-defined types (classes). This enables operators to work with objects in a natural way.

  • Unary Operators (++, --):
    class Point {
        int x, y;
    public:
        Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
        void display() { std::cout << "(" << x << ", " << y << ")" << std::endl; }
        Point operator++() { // Prefix ++
            ++x; ++y;
            return *this;
        }
        Point operator++(int) { // Postfix ++ (int is a dummy parameter)
            Point temp = *this;
            x++; y++;
            return temp;
        }
    };
  • Binary Operators (+, -, ==, !=, <<, >>):
    class Vector {
        int i, j;
    public:
        Vector(int _i = 0, int _j = 0) : i(_i), j(_j) {}
        Vector operator+(const Vector &other) const { // Binary +
            return Vector(i + other.i, j + other.j);
        }
        bool operator==(const Vector &other) const { // Binary ==
            return (i == other.i && j == other.j);
        }
        // Friend function for << (output stream)
        friend std::ostream &operator<<(std::ostream &out, const Vector &v) {
            out << "<" << v.i << ", " << v.j << ">";
            return out;
        }
        // Friend function for >> (input stream)
        friend std::istream &operator>>(std::istream &in, Vector &v) {
            std::cout << "Enter i and j: ";
            in >> v.i >> v.j;
            return in;
        }
    };

Data Conversion (Type Conversion between User-defined Types)

Allows conversion between user-defined types (classes) or between user-defined types and built-in types.

  • Basic Type to User-Defined Type (using constructor):
    class Kilograms {
        double kg;
    public:
        Kilograms(double val) : kg(val) {} // Constructor converts double to Kilograms
        // ...
    };
    Kilograms k = 5.5; // Implicit conversion from double to Kilograms
  • User-Defined Type to Basic Type (using conversion operator):
    class Pounds {
        double lbs;
    public:
        Pounds(double val) : lbs(val) {}
        operator double() { // Conversion operator to double
            return lbs * 0.453592; // Convert pounds to kg
        }
    };
    Pounds p(10);
    double kg_val = p; // Implicit conversion from Pounds to double
  • User-Defined Type to User-Defined Type: Can be achieved via a conversion constructor in the target class or a conversion operator in the source class.

Inheritance

A mechanism where a new class (derived/child class) inherits properties and behaviors from an existing class (base/parent class), promoting code reusability.

  • Single Inheritance: One derived class from one base class.
    class Animal { /* ... */ };
    class Dog : public Animal { /* ... */ };
  • Multiple Inheritance: One derived class from multiple base classes.
    class Father { /* ... */ };
    class Mother { /* ... */ };
    class Child : public Father, public Mother { /* ... */ };
  • Multilevel Inheritance: A derived class becomes a base class for another derived class.
    class Grandparent { /* ... */ };
    class Parent : public Grandparent { /* ... */ };
    class Child : public Parent { /* ... */ };
  • Hybrid Inheritance: A combination of two or more types of inheritance.
  • Multipath Inheritance (Diamond Problem): Occurs when a class inherits from two classes that themselves inherit from a common base class, leading to ambiguity in accessing members of the common base. Resolved using virtual inheritance.
    class A { /* ... */ };
    class B : virtual public A { /* ... */ };
    class C : virtual public A { /* ... */ };
    class D : public B, public C { /* ... */ }; // D has only one A sub-object

Constructor/Destructor in Single/Multilevel Inheritance (Order of Execution)

  • Constructors: Executed in the order of inheritance, from the most base class to the most derived class.
    class Base {
    public:
        Base() { std::cout << "Base Constructor\n"; }
        ~Base() { std::cout << "Base Destructor\n"; }
    };
    class Derived : public Base {
    public:
        Derived() { std::cout << "Derived Constructor\n"; }
        ~Derived() { std::cout << "Derived Destructor\n"; }
    };
    // When Derived obj; is created:
    // Output: Base Constructor, Derived Constructor
  • Destructors: Executed in the reverse order of constructor calls, from the most derived class to the most base class.
    // When Derived obj; goes out of scope:
    // Output: Derived Destructor, Base Destructor

3.5 Pure Virtual Function and File Handling

Virtual Function and Dynamic Binding

Virtual functions enable polymorphism in C++. When a base class pointer points to a derived class object, calling a virtual function through that pointer will execute the derived class's version of the function (dynamic/runtime polymorphism).

  • Virtual Function: Declared with the virtual keyword in the base class.
    class Base {
    public:
        virtual void show() { // Virtual function
            std::cout << "Base class show()\n";
        }
    };
    class Derived : public Base {
    public:
        void show() override { // Overriding the virtual function
            std::cout << "Derived class show()\n";
        }
    };
    
    int main() {
        Base *bptr;
        Derived d_obj;
        bptr = &d_obj;
        bptr->show(); // Calls Derived::show() due to virtual function
        return 0;
    }
  • Pure Virtual Function: A virtual function declared by assigning = 0 in the base class. It makes the base class an abstract class (cannot be instantiated). Derived classes must provide an implementation for pure virtual functions.
    class Shape { // Abstract class
    public:
        virtual void draw() = 0; // Pure virtual function
        virtual ~Shape() {}
    };
    class Circle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing Circle\n";
        }
    };
    // Shape s; // ERROR: cannot instantiate abstract class
    Shape *sptr = new Circle();
    sptr->draw(); // Calls Circle::draw()
    delete sptr;
  • Dynamic Binding (Late Binding): The decision of which function to call is made at runtime, based on the type of the object pointed to by the base class pointer. This is facilitated by a Virtual Table (vtable) and a Virtual Pointer (vptr). Each class with virtual functions has a vtable (a table of function pointers). Each object of such a class has a vptr, which points to its class's vtable.

Defining, Opening and Closing a File (fstream, ifstream, ofstream)

C++ uses stream classes for file I/O, defined in the <fstream> header.

  • ofstream: For writing to files.
    #include <fstream>
    // ...
    ofstream outFile("output.txt"); // Opens file for writing (truncates if exists)
    if (outFile.is_open()) {
        outFile << "Hello C++ File!";
        outFile.close();
    }
  • ifstream: For reading from files.
    #include <fstream>
    // ...
    ifstream inFile("output.txt"); // Opens file for reading
    if (inFile.is_open()) {
        std::string line;
        while (getline(inFile, line)) { // Read line by line
            std::cout << line << std::endl;
        }
        inFile.close();
    }
  • fstream: For both reading and writing.
    #include <fstream>
    // ...
    fstream myFile("data.txt", std::ios::out | std::ios::in | std::ios::trunc);
    // Opens for writing, reading, and truncates if exists.
    // Can specify modes like std::ios::app (append), std::ios::binary, etc.
    if (myFile.is_open()) {
        myFile << "Initial data\n";
        myFile.seekg(0); // Move read pointer to beginning
        std::string line;
        getline(myFile, line);
        std::cout << line << std::endl;
        myFile.close();
    }

Input/Output Operations on Files

Once a file stream object is created and opened, you can use stream insertion (<<) and extraction (>>) operators, similar to console I/O.

#include <fstream>
#include <string>

int main() {
    ofstream outFile("numbers.txt");
    outFile << 10 << " " << 20.5 << std::endl;
    outFile.close();

    ifstream inFile("numbers.txt");
    int i;
    double d;
    inFile >> i >> d; // Reads 10 and 20.5
    std::cout << "Read: " << i << ", " << d << std::endl;
    inFile.close();
    return 0;
}

Error Handling During I/O Operations

Stream objects have member functions to check for I/O errors:

  • is_open(): Checks if the file is successfully opened.
  • good(): Returns true if no errors have occurred.
  • fail(): Returns true if an I/O operation failed.
  • bad(): Returns true if a non-recoverable error occurred.
  • eof(): Returns true if the end-of-file has been reached.
  • clear(): Clears the error flags, allowing further I/O operations.
ifstream inFile("nonexistent.txt");
if (inFile.fail()) {
    std::cerr << "Failed to open file.\n";
    inFile.clear(); // Clear error flags if needed
}

Stream Class Hierarchy for Console I/O

C++ I/O is built on a hierarchy of classes in <iostream> and <fstream>:

  • ios_base: Base class for all stream classes, handles flags and formatting.
  • ios: Inherits from ios_base, adds error state information.
  • istream: For input operations (e.g., std::cin).
  • ostream: For output operations (e.g., std::cout, std::cerr, std::clog).
  • iostream: Inherits from both istream and ostream.
  • ifstream, ofstream, fstream: Derived from istream, ostream, and iostream respectively for file operations.

Unformatted I/O and Formatted I/O with ios member functions and flags

  • Unformatted I/O:
    • get(): Reads a single character or a block of characters.
    • put(): Writes a single character.
    • read(): Reads a block of binary data.
    • write(): Writes a block of binary data.
    char ch;
    std::cin.get(ch); // Reads one char
    std::cout.put(ch); // Writes one char
  • Formatted I/O with ios member functions and flags:

    You can control formatting using member functions like setf(), unsetf(), and flags(), often with constants defined in ios_base.

    std::cout.setf(std::ios::hex, std::ios::basefield); // Set hexadecimal output
    std::cout << 255 << std::endl; // Output: ff
    std::cout.unsetf(std::ios::hex); // Unset hexadecimal output
    
    std::cout.setf(std::ios::showpos); // Show '+' for positive numbers
    std::cout << 10 << std::endl; // Output: +10
    std::cout.unsetf(std::ios::showpos);

Formatting with Manipulators

Manipulators (from <iomanip>) provide a more convenient way to control formatting.

  • setw(width): Sets the field width for the next output.
    std::cout << std::setw(10