<<
operator<<
operator, so our custom types can stream information directly to the console using std::cout
So far, we've been using a simple terminal prompt to view the output of our program. Later in this course, we'll switch to rendering graphics using SDL.
But, there are some useful tricks we have that can help us make the most of our terminal for building out quick programs.
<<
OperatorWe've seen how we can stream numbers and strings to the terminal using the <<
operator:
std::cout << "Hello World " << 42;
We'd like to be able to do something similar with our custom objects. For example:
#include <iostream>
using namespace std;
class Character {
string Name { "Goblin Warrior" };
int Level { 15 };
int MaxHealth { 150 };
int Health { 90 };
};
int main() {
Character MyCharacter;
std::cout << MyCharacter;
}
To do this, we need to overload the <<
operator. Because we're calling <<
on the left operand, we need to overload it on the object type that is returned from std::cout
.
Inspecting that in our IDE, or checking the documentation, would reveal it to be a std::ostream
.
So, our overload would look like this:
void operator<< (
std::ostream& Stream,
Character& Character
) {
Stream << "Hello World";
}
Now, attempting to stream out a character will result in "Hello World" being streamed to the terminal.
Character MyCharacter;
std::cout << MyCharacter;
Hello World
We want to make two changes here:
Firstly, we want to be able to chain <<
operators. We'd like to do things like this:
std::cout << MyCharacter << std::endl;
To enable this, we need to make sure our function returns a reference to the stream we're writing to, so we can stream additional things to it. So, we update the return type of our function, and return the stream:
std::ostream& operator<< (
std::ostream& Stream,
Character& Character
) {
Stream << "Hello World";
return Stream;
}
With that, our code works:
Character MyCharacter;
std::cout << MyCharacter << "!!!";
Hello World!!!
Secondly, we want to stream out something that is appropriate to our character, rather than "Hello World".
A useful way of doing this is by adding a public method to our class. Lets imagine we wanted to log out this:
[Level 15] Goblin Warrior (90 / 150)
To accomplish this, our new Character.Write()
method might look something like this:
class Character {
public:
void Write(std::ostream& Stream) {
Stream << "[Level " << Level << "] ";
Stream << Name;
Stream << " (" << Health << " / " << MaxHealth << ")";
}
private:
std::string Name { "Goblin Warrior" };
int Level { 15 };
int MaxHealth { 150 };
int Health { 90 };
};
Finally, lets update our overload to use this function:
std::ostream& operator<< (
std::ostream& Stream,
Character& Character
) {
Character.Write(Stream);
return Stream;
}
Now, info about our characters can be streamed directly to our terminal:
std::cout << PlayerCharacter << std::endl;
std::cout << EnemyCharacter << std::endl;
Soon, we'll be working with libraries that help us create fully interactive user interfaces. But, before we move on to that, it's worth quickly covering how we can improve the terminal output.
Even though the products we ship to users aren't going to be interacted with on the terminal, Command Line Interface (CLI) is very common for internal tools, intended to be used by other developers.
Currently, our output has always been a vertical stream of text. Typically, a better user experience is update the existing output on the screen, rather than appending new information below the old.
A common technique to accomplish this is to clear the screen and redraw the new output.
There are several ways to clear a terminal, and it varies from operating system to operating system. Below is a simple example that works on Windows, Linux and Apple devices:
void ClearScreen() {
#if defined _WIN32
system("cls");
#elif
system("clear");
#endif
}
We can use this to create a simple clock application, that updates every second:
#include <chrono>
#include <iomanip>
#include <iostream>
#include <thread>
using namespace std;
using namespace std::chrono;
void ClearScreen() {
#if defined _WIN32
system("cls");
#elif
system("clear");
#endif
}
void PrintTime(const time_t& Time) {
tm TimeContainer;
localtime_r(&Time, &TimeContainer);
cout << put_time(&TimeContainer, "%r") << endl;
}
int main() {
while (true) {
time_point Time{system_clock::now()};
ClearScreen();
PrintTime(system_clock::to_time_t(Time));
this_thread::sleep_for(seconds(1));
}
}
Running this application, you may notice a small flicker where the screen is empty between updates. This would be especially noticable if we updated the screen more quickly, to simulate real time graphics.
The issue is caused simply by the time between clearing the screen, and drawing the new output. For a fraction of a second, there is nothing on the screen.
A common solution to this is a technique called double buffering. This involes two canvases, or buffers - the "front" buffer that is being shown to the user, and a "back" buffer where the next frame is being created.
Using this technique, the user only ever sees complete frames - the front buffer is shown until the back buffer is ready, then they get swapped out.
We cover this and other techniques for improving our visual output later in the course.
Next, we'll see how we can extend our use of the terminal to get input from our users, letting our software react to their input!
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way