blog icon indicating copy to clipboard operation
blog copied to clipboard

C++ Operator Overloading

Open qingquan-li opened this issue 1 year ago • 0 comments

Concept: C++ allows you to redefine how standard operators work when used with class objects.

1. Unary Operator Overloading

Overloading the unary minus (-) operator to negate the data members of a class.

#include <iostream>

class Number {
private:
    int value;
public:
    // Parameterized constructor
    // Initializes the value of the object to val
    // Number(int val) : value(val) {}
    // Same as:
    Number(int val) {
        value = val;
    }

    // Overload the unary minus operator to negate
    // the value of the object (the data members of a class)
    Number operator-() const {
        return Number(-value);
    }

    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    // Create a num object of type Number with the parameterized constructor
    Number num(5); // Initializes the num object to 5
    Number negated = -num; // Calls the overloaded unary minus operator
    negated.display();  // Displays "Value: -5"

    // Create a num2 object of int type
    int num2 = 5;
    int negated2 = -num2; // Calls the built-in unary minus operator
    std::cout << negated2 << std::endl; // -5

    return 0;
}

2. Binary Operator Overloading

Overloading the binary plus (+) operator to add objects of a class.

#include <iostream>

class MyNumber {
private:
    int value;
public:
    // Default constructor. Initializes the value of the object to 0
    // MyNumber() : value(0) {}

    // Parameterized constructor. Initializes the value of the object to val
    // MyNumber(int val) : value(val) {}
    // Same as:
    MyNumber(int val) {
        value = val;
    }

    /**
     * Overloads the + operator to add two MyNumber objects.
     *
     * This member function returns a new MyNumber object whose value
     * is the sum of the calling object's value and the passed object's value.
     *
     * @param other: A constant reference to another MyNumber object
     *               that we want to add to the current object.
     *
     * @return: Returns a new MyNumber object representing the sum
     *          of the current object and the passed object.
     */
    MyNumber operator+(const MyNumber& other) const {
        // The 'this' pointer points to the calling object.
        // Through the this pointer, we can access all members
        // (public, protected, and private) of that object.
        // In the context of this overloaded operator, the calling object is the one
        // on the left side of the + operator.
        // For example, in the expression 'a + b', if 'a' and 'b' are MyNumber objects,
        // 'a' is the calling object and 'b' is the 'other' object.

        // 'this->value' gets the value of the calling object.
        // 'other.value' gets the value of the 'other' object.

        // Create a new MyNumber object with the combined value.
        // return MyNumber(value + other.value);
        // Using this-> to make it clear that we are accessing the value of
        // the calling object (the member variable):
        return MyNumber(this->value + other.value);
    }

    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    // Create two objects of type MyNumber with parameterized constructors
    MyNumber num1(5); // Initializes the num1 object to 5
    MyNumber num2(10); // Initializes the num2 object to 10
    MyNumber sum = num1 + num2;  // Calls the overloaded + operator
    sum.display();  // Displays "Value: 15"

    // Create two objects of type int
    int num3 = 5;
    int num4 = 10;
    int sumOfNum3AndNum4 = num3 + num4;  // Calls the built-in + operator
    std::cout << sumOfNum3AndNum4 << std::endl;  // 15
    return 0;
}

3. Overloading the Stream Insertion Operator

Overloading the stream insertion operator (<<) for easy display of objects.

#include <iostream>

class Point {
private:
    int x, y;
public:
    // Parameterized constructor.
    // Initializes data members x and y to the values passed as arguments.
    // Point(int x, int y) : x(x), y(y) {}
    // Same as:
    Point(int x, int y) {
        this->x = x;
        this->y = y;
    }

    // Make it as a friend function of the class Point so that it can access
    // private data members x and y.
    friend std::ostream& operator<<(std::ostream& os, const Point& p);
};

/**
 * Overloading the stream insertion operator (<<) for the Point class.
 * This allows objects of the Point class to be easily printed using
 * standard C++ streams, such as std::cout.
 * @param os: a reference to the output stream where we want to send our formatted data.
 * @param p: a reference to the Point object we want to display.
 * @return a reference to the output stream.
 */
std::ostream& operator<<(std::ostream& os, const Point& p) {
    // Send a string to the output stream, 'os', with the format "Point(x, y)".
    // Access the private members 'x' and 'y' of the Point 'p'.
    // The use of 'p.x' and 'p.y' here is possible because this operator function
    // is declared as a friend of the Point class, allowing it to access private members.
    os << "Point(" << p.x << ", " << p.y << ")";

    // Return the modified output stream to allow for chained insertion operations.
    // For example: std::cout << point1 << point2;
    return os;
}

int main() {
    Point p(5, 10); // Instantiate a Point object (p) with x = 5 and y = 10
    std::cout << p << std::endl;  // Displays "Point(5, 10)"
    return 0;
}

4. Overloading the Assignment Operator

Creating a custom assignment operator for deep copying.

Note: Deep copy duplicates the actual data an object references, while shallow copy only duplicates the references to the data.

#include <iostream>

class Integer {
private:
    // A pointer to an integer.
    // This represents the value of the Integer object.
    int* value;
public:
    // Parameterized constructor that initializes the 'value' pointer
    // with a dynamically allocated integer.
    // Integer(int val) : value(new int(val)) {}
    // Same as:
    Integer(int val) {
        value = new int(val);
    }

    // Overloaded copy assignment operator to provide deep copy functionality.
    Integer& operator=(const Integer& other) {
        // First, check for self-assignment.
        // Comparing 'this' pointer with the address of the 'other' object.
        if (this != &other) {
            // If this isn't a self-assignment, free the memory of the current object.
            delete value;
            // Allocate new memory and copy the value from the 'other' object.
            value = new int(*(other.value));
        }
        // Return *this to allow for chained assignment like a = b = c.
        return *this;
    }

    // Display the value pointed by 'value' pointer.
    void display() const {
        std::cout << "Value: " << *value << std::endl;
    }

    // Destructor to free the dynamically allocated memory for 'value'
    // when the object is destroyed.
    ~Integer() {
        delete value;
    }
};

int main() {
    // Create two Integer objects with values 5 and 10 respectively.
    Integer num1(5);
    Integer num2(10);

    // Use the overloaded assignment operator to copy the value from 'num2' to 'num1'.
    num1 = num2;

    // Display the value of 'num1', which now should be 10 after the assignment.
    num1.display();  // Expected output: "Value: 10"

    return 0;
}

qingquan-li avatar Oct 11 '23 02:10 qingquan-li