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
Person
is defined with private member variables calledname
andage
.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, thename
andage
variables of thePerson
class objectp
are formatted and outputted to theostream
using the<<
operator.Line 23: The
operator<<()
function returns a reference to theostream
object, allowing for cascading, which means that multiple<<
operations can be chained together.Line 28: Using the
cout
object and the overloaded<<
operator, thep
object is printed to the console.
Note: The cascading effect is achieved by the returned reference to the
ostream
object, 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
Person
class object. Therefore, theoperator<<()
function enables custom printing of thePerson
class 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 theostream
object, facilitating cascading. In themain()
function, the overloadedoperator<<()
is used withcout
to print thePerson
class 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
v
is 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 streamout
to enable cascading, allowing us to chain multiple insertion operations.In the main function, an integer variable
var
is initialized with20
as 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 value
string. First, the overloaded insertion operator is called withvar
as 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 value
is displayed, resulting in the combined output by calling the already presentcout << string_message
already overloaded (due to cascading, thereturn out
statement).
We have added a practice exercise on changing the order for
cin
as 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 classDate
that 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.