...
/Miscellaneous Operators (<<, >>, [], ())
Miscellaneous Operators (<<, >>, [], ())
Discover the power of special ostream operators (<<, >>) that enable customized printing of user-defined data types using cout on both the console and file stream.
Overloading I/O operators
The I/O (Input/Output) objects cin/cout don't work for user-defined data types. Overloading I/O operators refers to the process of defining and customizing the operators (<< and >>) for user-defined data types. This allows us to control how our objects are formatted when outputting to a console or file streams or how they are read from a stream (iostream/ifstream).
When overloading I/O operators, we have two approaches: friend functions and global functions. We use friend functions when we need access to the private members of the class. To achieve this, we declare them as friends using the friend keyword, in their prototypes within the class and then define them outside the class. (We can define them inside the class as well.) This ensures that the functions can effectively work with the private members while maintaining proper encapsulation. As a result, we can elegantly customize I/O operations for user-defined data types in a clean and efficient manner.
For example, to overload the output operator (<<) for a user-defined MyClass class, we can define a friend function as follows:
#include <iostream>#include <string>using namespace std;class Person{string name;int age;public:Person(const string& name, int age) : name(name), age(age){}friend ostream& operator<<(ostream& out, const Person& person);// Prototype of the function};// Defining the friend function (which has access to private data members)ostream& operator<<(ostream& out, const Person& person){out << "Name: " << person.name << ", Age: " << person.age;return out;}int main(){Person p("John Doe", 25);cout << p << endl;/*// Exercise: Uncomment mecin>>p; // User should be able to enter : Maria 36cout << p; // modified p should be displayed : Name: Maria, Age: 36*/return 0;}
In the friendFunction.cpp file:
We implement the operator<<() function using the friend keyword.
Lines 6–9: A class called
Personis defined with private member variables callednameandage.Line 15: The
ostream& operator<<()function prototype is declared. Notice that this is just a prototype.Lines 20–24: Inside the implementation of the
operator<<()function, thenameandagevariables of thePersonclass objectpare formatted and outputted to theostreamusing the<<operator.Line 23: The
operator<<()function returns a reference to theostreamobject, allowing for cascading, which means that multiple<<operations can be chained together.Line 28: Using the
coutobject and the overloaded<<operator, thepobject is printed to the console.
Note: The cascading effect is achieved by the returned reference to the
ostreamobject, enabling additional output statements to be chained with the<<operator, allowing multiple pieces of information to be printed on a single line.
In the globalFunction.cpp file:
The only difference in this implementation is that it uses getters to implement the printing of the
Personclass object. Therefore, theoperator<<()function enables custom printing of thePersonclass objects by overloading the<<operator. It takes anostream&as the output stream and aconst Person&as the object to be printed. The function returns a reference to theostreamobject, facilitating cascading. In themain()function, the overloadedoperator<<()is used withcoutto print thePersonclass object, showcasing cascading by appending additional output statements. Similar to this, we can easily overload an input stream operator>>as well (both for console and file). We’re leaving that as a practice exercise for you.
Exercise: Overloading the >> operator (adding the operator>>() function)
Write two separate implementations using friend and global functions to enable taking a person as input from the user. If you have trouble getting to the solution, you can click the “Show Hint” button to see how to implement it.
I/O operator overloading and adding new features in cout
We already have used istream global object cin and ostream global object cout for primitive data types, usually using the following syntax:
cin >> primitive_DataType_Variable1 >> primitive_DataType_Variable1;cout << primitive_DataType_Variable1 << primitive_DataType_Variable2;
Their behavior is actually defined in the C++ standard libraries (istream and ostream). We can build on that and add further functionality. For example, what if instead of writing the parameter to be printed to the right of the cout statement, we want to add the functionality that we would like to print with the following syntax?
primitive_DataType_Variable >> cin;primitive_DataType_Variable1 << cout;
What can we do to add this functionality using the already given functionality of the cin >> and cout << statements?
Here’s what we can do:
#include <iostream>using namespace std;// Overloading the insertion operator (<<) for integersostream& operator<<(const int& v, ostream& out){out << v; // Stream the integer value into the output streamreturn out; // enabling the cascading}int main(){int var = 20;var << cout << " is the value"; // Outputs: 20 is the value// This will call the above operator function// and then due to cascading it will display the stringcout << var << " is the value"; // Outputs: 20 is the value/*// Fun exercise: Add the functionality for printing the following linecout << "Enter new value: ";var >> cin;cout << "New Value is; " << var << endl;*/return 0;}
The code defines a new version of the insertion
operator <<that takes an integer (const int&) as the left operand and an output stream (ostream&) as the right operand. This version of the operator is a nonmember function (global function) since it is defined outside the class.Inside the overloaded operator function, the integer value
vis streamed into the output stream (out) using theout << v;statement. This allows us to customize how integers are displayed in the output stream. The function then returns the output streamoutto enable cascading, allowing us to chain multiple insertion operations.In the main function, an integer variable
varis initialized with20as its value. The overloaded insertion operator is utilized twice to print the value of the variable and a custom message:var << cout << " is the value";: This code line outputs the20 is the valuestring. First, the overloaded insertion operator is called withvaras the left operand, andcout(the output stream) as the right operand. The value ofvar(i.e.,20) is streamed intocout. Then, the custom messageis the valueis displayed, resulting in the combined output by calling the already presentcout << string_messagealready overloaded (due to cascading, thereturn outstatement).
We have added a practice exercise on changing the order for
cinas well. You’re encouraged to try it for fun.
The provided overloaded insertion operator for integers enables us to employ a new syntax for displaying integers, enhancing the versatility and expressiveness of the cout statement. This demonstrates the power of operator overloading, which allows us to define custom behavior for operators, introducing functionalities that were not present before.
However, it’s essential to note that operator overloading doesn’t allow us to override the existing implementations of operators, such as adding two integers or floats. We can’t change the fundamental behavior of standard operators like +, -, *, and so on, for primitive data types. Instead, we can only define custom behaviors for these operators when used with our user-defined data types, offering a way to handle data in a manner that suits our specific needs.
Miscellaneous operators
In this topic, we’ll discuss two miscellaneous operators: the subscript operator ([]), the function call operator (()). These operators serve unique purposes and can be overloaded to provide custom behavior within a class.
1. Subscript operator (
[]): The subscript operator ([]) allows objects to be accessed using array-like syntax. It's commonly used to access elements within a class that represent a collection or a sequence. By overloading this operator, we can define custom behavior for accessing and modifying elements of an object. Suppose we have a classDatethat represents a date with day, month, and year components. We can overload the subscript operator to allow access to these components using the subscript notation.2. Function call operator (
()): The function call operator (()) enables objects to be invoked or called as if they were functions. By overloading this operator, we can define custom behavior for invoking an object as a function.
Let’s look at an example to cover these two operators.
#include <iostream>using namespace std;class Date{private:int day;int month;int year;public:Date(int d, int m, int y) : day(d), month(m), year(y){}int& operator[](int index){if (index == 0){return day;}else if (index == 1){return month;}else if (index == 2){return year;}else{throw out_of_range("Invalid index for Date");}}void operator()(){cout << "Today's date is: " << day << "/" << month<< "/" << year << endl;}};int main() {Date date(14, 6, 2023);// Using the subscript operatorcout << "Day: " << date[0] << endl;cout << "Month: " << date[1] << endl;cout << "Year: " << date[2] << endl;// Using the function call operatordate();// Modifying the date using the subscript operatordate[0] = 15;date[1] = 7;date[2] = 2023;date(); // Updated datereturn 0;}
In summary, there are several other operators that we can overload, like unary operators ('-', '!'). There are several other bitwise operators too, like &, |, ^, ~, &=, |=, ^=, ~=, and so on. We may use them appropriately by looking at their parameter requirements and defining them in their own fashion, however needed.