Categories
Computer Science / Information Technology Language: C++

Input-Ouput Streams

Overview

In this section, we will explore the utilisation of streams for IO in C++. Firstly, we will delve into the concept of streams and their role in simplifying the handling of diverse input and output devices. Next, we will familiarise ourselves with various C++ stream manipulators. These manipulators are functions that impact the way we read from and write to strings. Following that, we will study a selection of C++ stream manipulators which specifically affect stream-based input and output operations. We will cover manipulators designed for Boolean, integer, and floating-point values. Additionally, we will employ manipulators applicable to any data type to aid us in formatting our output. Such manipulators provide us with capabilities to align text, fill blank spaces with designated characters, and control the width of output fields.

Once we conclude our exploration of stream manipulators, we will shift our focus to input files. This includes topics like file opening, successful opening verification, reading from files, and closing files. We will explore both formatted and unformatted input/output techniques for file processing.

Subsequently, we will delve into output files and their mechanisms. We will learn how to open output files, write to them using formatted and unformatted modes, and close them.

To conclude this section, we will explore string streams. This enables us to treat in-memory strings as streams and utilise the same IO techniques we apply to file-based streams. The power of in-memory string streams will be showcased, including their utility for data validation.

Files, Streams and I/O

Let’s explore how C++ utilises streams and files for input and output (IO) operations. Developing an IO library for any programming language is an exceptionally challenging task due to the multitude of devices that can provide data to a program and receive data from it. These devices encompass physical entities like hard disks, consoles, and keyboards, as well as virtual devices such as connections to web servers.

In C++, a stream abstraction is provided to facilitate working with IO devices. A stream serves as an interface that remains independent of the specific device. Consequently, programmers can code to the stream interface without concerning themselves too much with the connected device. Conceptually, a stream is akin to a sequence of bytes. C++ offers various stream types depending on whether input or output is desired, although there are streams that perform both functions simultaneously.

To visualise this, let’s refer to the following diagram.

On the right side, we see a C++ program. The upper stream represents an input stream that supplies input to the program from an input device. This device could be the keyboard, a file, a connection to a web service, or others. The lower stream represents an output stream that receives output from the program and transmits it to an output device. Similarly, the output device can be a file, the console, a connection to a web service, or others.

The following table showcases three frequently used header files for stream IO:

Header fileDescription
iostreamProvides the necessary definitions for formatted input and output to and from streams
fstreamEquips us with definitions for formatted IO with file streams
iomanipincludes definitions for manipulators that facilitate specific formatting of iostreams

Upon including these header files, we gain access to several C++ classes that facilitate file IO operations. The following table discusses a few of them:

ClassDescription
iosOffers fundamental support for formatted and unformatted IO and serves as the base class for most other classes in the iostream hierarchy.
ifstreamEnables high-level input operations from files. If you intend to read from a file, you can declare an object of the ifstream class.
ofstreamFacilitates high-level output operations to files. When creating a new file or writing to an existing one, you can declare an object of the ofstream class.
fstreamEnables high-level IO on file-based streams. It inherits from both ifstream and ofstream using multiple inheritance. By using an fstream object, we can perform both input and output operations on a file simultaneously.
stringstreamParticularly useful for high-level IO on strings stored in memory. Similar to using insertion and extraction operators with cin and cout, we can employ them with strings to obtain input and produce output within an in-memory string.

Now, let’s revisit familiar entities: cin and cout, and understand their nature. Although we have been using them without worrying about their creation or connection, it is a testament to the design of the C++ IO library and the device independence model.

Global stream objects

cin, cout, cerr, and clog are global objects that are initialised before the execution of the main function. To utilise them, we simply include the “iostream” header file.

ObjectDescription
cinServes as the standard input stream and is connected to the keyboard by default. It is an instance of the istream class.
coutRepresents the standard output stream and is connected to the console by default. It is an instance of the ostream class.
cerrRepresents the standard error stream and is connected to the console by default. It is an instance of the ostream (unbuffered) class.
clogRepresents the standard log stream and is connected to the console by default. It is an instance of the ostream (unbuffered) class.

cin and cout are typically buffered streams. This means that input from cin is not processed automatically until the user presses the Enter key. Similarly, output to cout occurs when the stream buffer becomes full, when we provide a std::endl, or when we explicitly flush the stream.

cerr and clog are unbuffered, meaning that input or output is handled immediately as needed. It is considered best practice to use cerr for error messages and clog for log messages.

For those familiar with the UNIX terminal shell or the Windows command prompt, it is worth noting that these streams can be easily redirected. This allows input to come from a file or output to be directed to a file. While we won’t cover the details here, It is worth it to search online for resources on redirecting IO if interested. You will discover how straightforward and powerful this capability can be.

C++ Stream Manipulators

C++ streams offer a range of helpful member methods that allow us to control formatting. While there are methods available for both input and output streams, formatting output is more commonly used. It’s important to understand that when we manipulate a stream for formatting, the settings we apply can have different durations. Some settings will last for the remainder of the program, while others only affect the next object placed on the stream. There are also settings that take effect immediately.

Most stream formatters come in two versions: a method version and a manipulator version. The method version involves calling a method on the stream object itself. For example, 

std::cout.width(10);

We shall revisit this method in detail later, but for now, let’s focus on the syntax. On the other hand, the manipulator version is designed to be used inline as part of a stream insertion. For instance,

std::cout << std::setw(10);

The manipulator versions leverage the overloading of the insertion operator to provide convenience. To utilise the manipulators, it is necessary to include the iomanip header file.

Boolean stream manipulators

When displaying Boolean values in C++, the default behaviour is to output a 1 for true and a 0 for false. However, there are situations where we might prefer to display the strings “true” or “false” instead. Instead of writing if-else logic every time we need to format Boolean values, C++ provides convenient stream manipulators to control the display format.

Let’s explore examples of formatting Boolean types using cout as the output stream. Keep in mind that these manipulators can be used with any output stream, including file output streams.

// Default behaviour for Boolean values
cout << (10 == 10) << endl;   // Output: 1 (true)
cout << (10 == 20) << endl;   // Output: 0 (false)

To switch to the “true/false” mode, we can use the boolalpha manipulator:

// Formatting Boolean values as "true" or "false"
cout << boolalpha;    // Set the output stream to bool alpha mode
cout << (10 == 10) << endl;   // Output: true
cout << (10 == 20) << endl;   // Output: false

Once we set the boolalpha mode, all subsequent Boolean values will be displayed accordingly. To toggle back to the default behaviour, we can use the noboolalpha manipulator:

// Switching back to default Boolean formatting
cout << noboolalpha;    // Reset the output stream to default
cout << (10 == 10) << endl;   // Output: 1
cout << (10 == 20) << endl;   // Output: 0

Alternatively, we can use the setf method to set the formatting for Boolean types:

// Setting Boolean formatting using setf method
cout.setf(ios::boolalpha);    // Set the bool alpha format flag
cout << (10 == 10) << endl;   // Output: true
cout << (10 == 20) << endl;   // Output: false

To reset the formatting flag back to the default, we can use one of the following 2 ways:

  1. The unsetf method:
// Resetting Boolean formatting using unsetf method
cout.unsetf(ios::boolalpha);    // Reset the bool alpha format flag
cout << (10 == 10) << endl;   // Output: 1
cout << (10 == 20) << endl;   // Output: 0
  1. The resetiosflags method:
// Resetting Boolean formatting using resetiosflags method
cout << resetiosflags (ios::boolalpha);  // Reset the bool alpha format flag
cout << (10 == 10) << endl;   // Output: 1
cout << (10 == 20) << endl;   // Output: 0

While both methods (setf and unsetf) can be used, the stream manipulator version (boolalpha and noboolalpha) is more commonly used and recommended for simplicity and readability.

Here’s an example that demonstrates the usage of setf, unsetf, boolalpha, noboolalpha and resetiosflags:

#include <iostream>
#include <iomanip>

int main() {
    bool value = true;

    // Display the Boolean value using the default formatting
    std::cout << "Default Formatting: " << value << std::endl;

    // Display the Boolean value using boolalpha manipulator
    std::cout << "boolalpha Formatting: " << std::boolalpha << value << std::endl;

    // Toggle the formatting using noboolalpha manipulator
    std::cout << "noboolalpha Formatting: " << std::noboolalpha << value << std::endl;

    // Display the Boolean value using setf method
    std::cout.setf(std::ios::boolalpha);
    std::cout << "setf Formatting: " << value << std::endl;

    // Reset the formatting back to default using unsetf method
    std::cout.unsetf(std::ios::boolalpha);
    std::cout << "unsetf Formatting: " << value << std::endl;
    
    // Display the Boolean value using setf method
    std::cout.setf(std::ios::boolalpha);
    std::cout << "setf Formatting: " << value << std::endl;
    
    // Display the default Boolean value using resetiosflags method
    std::cout << std::resetiosflags(std::ios::boolalpha);
    std::cout << "resetiosflags Formatting: " << value << std::endl;

    return 0;
}

Output:

Default Formatting: 1
boolalpha Formatting: true
noboolalpha Formatting: 1
setf Formatting: true
unsetf Formatting: 1
setf Formatting: true
resetiosflags Formatting: 1

Explanation:

  1. The code starts by including the necessary headers <iostream> and <iomanip> for input/output operations and manipulators.
  2. Inside the main() function, a Boolean variable value is declared and initialized with the value true.
  3. The first output statement uses the default formatting for Boolean values. It displays the message “Default Formatting:” followed by the value of value, which will be 1 for true.
  4. The second output statement demonstrates the use of the std::boolalpha manipulator. By inserting std::boolalpha before value, it enables the boolalpha formatting flag for the subsequent output operation. The output displays the message “boolalpha Formatting:” followed by the value of value, which is now displayed as the string “true”.
  5. The third output statement uses the std::noboolalpha manipulator to toggle the formatting back to the default. The output displays the message “noboolalpha Formatting:” followed by the value of value, which reverts to displaying 1 for true.
  6. The fourth output statement uses the std::cout.setf() method to set the boolalpha formatting flag explicitly. This enables the boolalpha formatting for subsequent output operations without using the manipulator. The output displays the message “setf Formatting:” followed by the value of value, which is displayed as “true”.
  7. The fifth output statement uses the std::cout.unsetf() method to unset the boolalpha formatting flag, effectively resetting the formatting back to the default. The output displays the message “unsetf Formatting:” followed by the value of value, which reverts to displaying 1 for true.
  8. The sixth output statement uses the std::cout.setf() method again to set the boolalpha formatting flag. The output displays the message “setf Formatting:” followed by the value of value, which is displayed as “true” again.
  9. The seventh output statement demonstrates the use of std::resetiosflags() function with the std::ios::boolalpha flag. It resets the boolalpha formatting flag back to the default state for the std::cout stream. The output displays the message “resetiosflags Formatting:” followed by the value of value, which reverts to displaying 1 for true.

Exercises

  1. Write a program that prompts the user to enter a boolean value (true or false) and displays it using the boolalpha manipulator.
  2. Write a program that reads a boolean value from the user and displays it in uppercase using the uppercase manipulator.
  3. Write a program that reads a boolean value from the user and displays it in lowercase using the nouppercase manipulator.
  4. Write a program that prompts the user to enter two boolean values and displays them side by side, separated by a tab character, using the boolalpha manipulator.
  5. Write a program that reads a boolean value from the user and displays it as “Yes” if it’s true and “No” if it’s false using the boolalpha and true/false stream manipulators.

Integer stream manipulators

In C++, there are various formatting options available for displaying integers when outputting to a stream. Let’s explore these options and see how they can be used.

By default, integers are displayed in base 10, which is the decimal system commonly used by humans. However, we have the flexibility to display integers in other bases as well. Octal (base 8) and hexadecimal (base 16) are two commonly used alternate bases in computing. To demonstrate these formatting options, let’s consider the following code snippet:

#include <iostream>
#include <iomanip>

int main() {
    int num = 255;

    // Display integer in decimal (base 10) - default formatting
    std::cout << "Decimal (default): " << num << std::endl;

    // Display integer in hexadecimal (base 16)
    std::cout << "Hexadecimal: " << std::hex << num << std::endl;

    // Display integer in octal (base 8)
    std::cout << "Octal: " << std::oct << num << std::endl;

    return 0;
}

Output:

Decimal (default): 255
Hexadecimal: ff
Octal: 377

In the above code, we initialise the integer num to the value 255. Then, we use manipulators (std::hex and std::oct) to change the base of the output stream.

As you can see, changing the base using the manipulators affects the subsequent output operations on the stream. The std::hex manipulator formats the integer as a hexadecimal value (ff in this case), and the std::oct manipulator formats it as an octal value (377 in this case).

To make it clear that the values are in hexadecimal or octal, we can use the std::showbase manipulator. Let’s modify the code to include this manipulator:

#include <iostream>
#include <iomanip>

int main() {
    int num = 255;

    // Display integer in decimal (base 10) - default formatting
    std::cout << "Decimal (default): " << num << std::endl;

    // Display integer in hexadecimal (base 16) with prefix
    std::cout << "Hexadecimal (with prefix): " << std::showbase << std::hex << num << std::endl;

    // Display integer in octal (base 8) with prefix
    std::cout << "Octal (with prefix): " << std::showbase << std::oct << num << std::endl;

    return 0;
}

Output:

Decimal (default): 255
Hexadecimal (with prefix): 0xff
Octal (with prefix): 0377

With the std::showbase manipulator, the hexadecimal value is displayed with the 0x prefix, indicating that it’s in hexadecimal format. Similarly, the octal value is displayed with a leading 0 to indicate its base. This can be toggled back using std::noshowbase manipulator.

Additionally, we can control the case of hexadecimal digits and the plus sign for positive integers. Let’s modify the code to include these options:

#include <iostream>
#include <iomanip>

int main() {
    int num = 255;

    // Display integer in hexadecimal (base 16) with uppercase letters
    std::cout << "Hexadecimal (uppercase): " << std::showbase << std::uppercase << std::hex << num << std::endl;

    // Display integer in decimal (base 10) with plus sign
    std::cout << "Decimal (with plus sign): " << std::showpos << std::dec << num << std::endl;

    return 0;
}

Output:

Hexadecimal (uppercase): 0XFF
Decimal (with plus sign): +255

By using the std::uppercase manipulator, the hexadecimal digits are displayed in uppercase (FF in this case). When we combine it with std::showpos, a plus sign is added in front of positive integers. We can toggle back this setting using std::noshowpos to not show the positive sign.

Remember that these formatting options persist for future integer output operations unless changed or reset. To reset the formatting flags to their defaults, you can use the std::resetiosflags() function or the std::cout.flags() method. This is demonstrated in the following code snippet:

#include <iostream>
#include <iomanip>

int main() {
    int num = 255;

    // Display integer in hexadecimal (base 16) with prefix
    std::cout << "Hexadecimal (with prefix): " << std::showbase << std::hex << num << std::endl;

    // Reset the formatting flags to default
    std::cout.flags(std::ios_base::dec);

    // Display the same integer using the default formatting
    std::cout << "Default Formatting: " << num << std::endl;

    // Display integer in hexadecimal (base 16) with prefix
    std::cout << "Hexadecimal (with prefix): " << std::showbase << std::hex << num << std::endl;

    // Reset the base formatting flag to default
    std::cout << std::resetiosflags(std::ios_base::basefield);

    // Display the same integer using the default formatting
    std::cout << "Default Formatting: " << num << std::endl;

    return 0;
}

Output:

Hexadecimal (with prefix): 0xff
Default Formatting: 255
Hexadecimal (with prefix): 0xff
Default Formatting: 255

Binary equivalent value generation

As we have seen manipulating integers to hex and octal values in the stream, it is useful to know how to manipulate the given integer to binary too. We can achieve this using bitset. The following example demonstrates the same:

std::cout << "42 in binary:  " << std::bitset<8>{42} << std::endl;
// output : The number 42 in binary:  00101010

Exercises

  1. Write a program that prompts the user to enter an integer and displays it in hexadecimal format.
  2. Create a program that calculates the factorial of a given positive integer. Prompt the user to enter the integer. Display the factorial in decimal format.
  3. Write a program that converts a binary number to its decimal equivalent. Prompt the user to enter a binary number as a string. Display the decimal equivalent using integer stream manipulators. Hint: Use u_long() method of string class.
  4. Create a program that determines whether a given integer is even or odd. Prompt the user to enter an integer. Display “Even” or “Odd” using integer stream manipulators.
  5. Write a program that converts a decimal number to its binary representation. Prompt the user to enter a decimal number. Display the binary representation using integer stream manipulators.

Floating point stream manipulators

Stream formatting options are available for formatting floating-point numbers. One of the key aspects is controlling the precision of the displayed number. By default, the precision is set to 6 digits. Let’s explore some examples and code snippets to understand these concepts better.

Defaults

To begin, let’s consider precision. By default, the precision determines the number of digits displayed after the decimal point. For instance, if we have the number 1234.5678, and we display it in C++, the default precision of 6 will round the number and display it as 1234.57.

#include <iostream>
#include <iomanip>
int main() {
    double num = 1234.5678;
    std::cout << num << std::endl;
    return 0;
}

Output:

1234.57

If the precision is insufficient to represent the number accurately, C++ will resort to scientific notation. Let’s consider another example with a larger number, 123456789.987654321:

#include <iostream>
#include <iomanip>
int main() {
    double num = 123456789.987654321;
    std::cout << num << std::endl;
    return 0;
}

Output:

1.23457e+08

In this case, the number is displayed using scientific notation (1.23457e+08) since using the default precision of 6 would not accurately represent the number. The precision still remains 6, but scientific notation is employed.

std::scientific

C++ provides stream manipulator std::scientific to manipulate the number to be streamed in scientific format. The following example demonstrates the same:

#include <iostream>
#include <iomanip>
int main() {
    double num = 1234.5678;
    std::cout << "Without setting std::scientific: " << num << std::endl;
    std::cout << "After setting std::scientific: " << std::scientific << num << std::endl;
    return 0;
}

Output:

Without setting std::scientific: 1234.57
After setting std::scientific: 1.234568e+03

std::setprecision()

To set a specific precision, we can use the std::setprecision() manipulator. For example, if we want to display the number with a precision of 9, we can modify the code as follows:

#include <iostream>
#include <iomanip>
int main() {
    double num = 123456789.987654321;
    std::cout << std::setprecision(9) << num << std::endl;
    return 0;
}

Output:

123456789

In this case, the number is displayed with a precision of 9, and rounding occurs. Note that no trailing zeros are displayed by default.

std::fixed

Next, let’s explore the std::fixed manipulator. When used, precision is determined from the right side of the decimal point. For example:

#include <iostream>
#include <iomanip>
int main() {
    double num = 1234.5678;
    std::cout << std::fixed << std::setprecision(6) << num << std::endl;
    return 0;
}

Output:

1234.567800

In this case, the number is displayed with exactly 6 digits after the decimal point, and trailing zeros are added if necessary. The std::fixed manipulator ensures precision is applied starting from the decimal point.

std::showpos

Using the std::showpos manipulator with floating-point numbers behaves similarly to integers. It adds a preceding plus sign for positive numbers. Here’s an example:

#include <iostream>
#include <iomanip>
int main() {
    double num = 1234.5678;
    std::cout << std::showpos << num << std::endl;
    return 0;
}

Output:

+1234.56

std::showpoint

By default, trailing zeros are not displayed for floating-point numbers. However, we can use the std::showpoint manipulator to include trailing zeros based on the precision used. Consider the following example:

#include <iostream>
#include <iomanip>
int main() {
    double num = 12.34;
    std::cout << std::showpoint << std::setprecision(6) << num << std::endl;
    return 0;
}

Output:

12.3400

To reset the floating-point format back to the general format, you have two options. One way is to use the std::unsetf method:

#include <iostream>
#include <iomanip>

int main() {
    double num = 1234.5678;
    std::cout << std::setprecision(6) << std::scientific << num << std::endl;
    std::cout.unsetf(std::ios::fixed | std::ios::scientific);
    std::cout << num << std::endl;

    return 0;
}

Output:

1.234568e+03
1234.57

Alternatively, you can use the std::resetiosflags manipulator:

std::cout << std::resetiosflags(std::ios::fixed | std::ios::scientific);

Exercises

Exercise 1:

Write a program that prompts the user to enter a floating-point number and displays it in scientific notation with a precision of 3 digits after the decimal point.

Exercise 2:

Create a program that calculates the average of three test scores. Prompt the user to enter the scores as floating-point numbers. Display the average with a precision of 2 digits after the decimal point and in fixed-point notation.

Exercise 3:

Write a program that converts a distance in kilometres to miles. Prompt the user to enter the distance as a floating-point number. Display the result in fixed-point notation with a precision of 1 digit after the decimal point.

Exercise 4:

Create a program that calculates the compound interest on an investment. Prompt the user to enter the principal amount, interest rate (as a percentage), and the number of years. Display the final amount with a precision of 2 digits after the decimal point and in fixed-point notation.

Exercise 5:

Write a program that prompts the user to enter a temperature in Celsius and converts it to Fahrenheit. Display the result with a precision of 1 digit after the decimal point and in fixed-point notation.

Align and fill stream manipulators

We’ll explore some of the stream formatting options available that work with any type of data. These options allow us to define a field width and specify the justification and fill character for the data items. Let’s go through some examples and code snippets to understand these concepts better.

Default behaviour

By default, there is no field width defined, and output occurs right after the previous output. For example:

#include <iostream>
int main() {
    double num = 1234.5678;
    std::string hello = "hello";
    std::cout << num << " " << hello << std::endl;
    return 0;
}

Output:

1234.5678 hello

Field Width (std::setw()):

The std::setw() manipulator sets the field width for the next data item in the stream. It specifies the minimum number of characters that will be allocated for the data item’s display. If the data item requires fewer characters, it will be padded to meet the specified width.

#include <iostream>
#include <iomanip>

int main() {
    int num = 123;
    std::string text = "Hello";

    std::cout << std::setw(8) << num << std::endl;
    std::cout << std::setw(8) << text << std::endl;

    return 0;
}

Output:

     123
   Hello

In this example, std::setw(8) sets the field width to 8 characters for both the num and text data items. The resulting output ensures that each item is displayed within its respective field width.

To set a field width for multiple data items, we can use std::setw() before each data item. For example:

#include <iostream>
#include <iomanip>

int main() {
    double num = 1234.5678;
    std::string hello = "hello";

    std::cout << std::setw(10) << num << std::setw(10) << hello << std::setw(10) << hello << std::endl;

    return 0;
}

Output:

   1234.57      hello     hello

In this case, each data item is displayed within a field width of 10, right-justified by default. The field width and justification settings only apply to the next data item placed on the stream.

Alignment (std::left and std::right):

The std::left and std::right manipulators control the alignment of data within the specified field width. By default, data is right-aligned within the field. std::left makes the data left-aligned within the field width.

#include <iostream>
#include <iomanip>

int main() {
    int num = 123;
    std::string text = "Hello";

    std::cout << std::setw(8) << std::left << num << std::endl;
    std::cout << std::setw(8) << std::left << text << std::endl;

    return 0;
}

Output:

123
Hello

In this example, std::left is used to left-align the num and text data items within their respective field widths.

Fill Character (std::setfill())

The std::setfill() manipulator sets the fill character for padding the unused space within the specified field width. By default, the fill character is a space.

#include <iostream>
#include <iomanip>

int main() {
    int num = 123;
    std::string text = "Hello";

    std::cout << std::setw(8) << std::setfill('.') << num << std::endl;
    std::cout << std::setw(8) << std::setfill('-') << text << std::endl;

    return 0;
}

Output:

.....123
---Hello

In this example, std::setfill('.') and std::setfill('-') are used to set the fill characters to a dot (.) and a dash (-), respectively. The fill characters are used to pad the unused space within the field widths.

Resetting the manipulation

Similar to the previous manipulators, these stream manipulators also retain the manipulation if not reset. To reset, std::resetiosflags() can be used. The following examples demonstrates its usage:

#include <iostream>
#include <iomanip>

int main() {
    int num = 123;
    std::string text = "Hello";

    // Set field width, alignment, and fill character
    std::cout << std::setw(8) << std::setfill('.') << num << std::endl;
    std::cout << std::setw(8) << std::setfill('-') << text << std::endl;

    // Reset field width, alignment, and fill character
    std::cout << std::resetiosflags(std::ios::adjustfield);  // Reset alignment
    std::cout << std::resetiosflags(std::ios::basefield);    // Reset field width
    std::cout << std::setfill(' ');                          // Reset fill character

    // Output with default settings
    std::cout << num << std::endl;
    std::cout << text << std::endl;

    return 0;
}

Output:

.....123
---Hello
123
Hello

By combining these manipulators, you can achieve precise control over the field width, alignment, and fill character for each data item in the stream. These manipulators help in formatting the output in a visually appealing manner.

Exercises

Exercise 1:

Write a program that asks the user to enter three integers and displays them in a table format. Each number should be right-aligned in a field width of 8 characters, and the empty spaces should be filled with ‘0’.

Example:

Enter three integers: 12 345 6
00000012 00000345 00000006

Exercise 2:

Create a program that calculates the area and perimeter of a rectangle. Prompt the user to enter the length and width of the rectangle as floating-point numbers. Display the results with a precision of 2 digits after the decimal point. The numbers should be left-aligned in a field width of 10 characters, and the empty spaces should be filled with ‘#’.

Exercise 3:

Write a program that displays a countdown from 10 to 1. Each number should be centred in a field width of 5 characters, and the empty spaces should be filled with *.

Exercise 4:

Create a program that converts temperature from Celsius to Fahrenheit. Prompt the user to enter the temperature in Celsius as a floating-point number. Display the result with a precision of 1 digit after the decimal point. The number should be right-aligned in a field width of 6 characters, and the empty spaces should be filled with +.

Exercise 5:

Write a program that prints a pattern of asterisks in a pyramid shape. Prompt the user to enter the number of rows for the pyramid. Each asterisk should be centred in a field width of 3 characters, and the empty spaces should be filled with -.

File operations

For file IO operations using C++ streams, we can use the ifstream, ofstream and fstream classes.

The ifstream class

The ifstream class in C++ is a derived class of the istream (need not be looked at as ifstream is more powerful) class and is specifically designed for reading input from files. It provides a convenient interface to read data from text files, allowing programmers to perform various operations such as reading lines, extracting formatted data, or reading characters from a file.

With the ifstream class, you can easily open a file for input, read data from it, and perform input operations just like you would with the standard input stream cin. It supports input operations such as extracting data using the extraction operator >>, reading lines with getline, or reading characters using member functions like get or getline.

The ifstream class inherits all the functionality of the base istream class and provides additional member functions specifically tailored for file input operations. It allows you to handle file input in a convenient and efficient way, making it an essential tool for reading data from files in C++ programs.

Step 1: Include the necessary header file

To work with file streams, we need to include the <fstream> header file at the beginning of our code. This provides the necessary classes and functions for file input/output operations.

#include <fstream>

Step 2: Declare and connect the stream object

We declare a stream object of either fstream or ifstream type, depending on whether we need both input and output or just input operations. Then, we connect the stream object to the file we want to read from. As we are just reading from the file, we shall use ifstream class.

std::ifstream infile("filename.txt");
// or
std::ifstream infile {"filename.txt"};
// or
std::ifstream infile;
infile.open("filename.txt");

Note that you can also mention the path to the file if the file is not present in the same directory as that of the executable of the program. There is a second optional parameter along with the filename, that is the flag/mode of opening the specified file. They are briefly explained as follows:

  1. std::ios::in

Indicates that the file should be opened for input. It is the default mode for ifstream and allows reading from the file. You can omit this flag when opening an input file.

std::ifstream infile;
infile.open("input.txt", std::ios::in);
  1. std::ios::binary

This flag specifies that the file should be opened in binary mode. It is useful when reading non-text files or when working with raw binary data.

std::ifstream binaryFile;
binaryFile.open("data.bin", std::ios::in | std::ios::binary);

Note that we can specify multiple modes separated by the bitwise OR operator.

  1. std::ios::ate

This flag stands for “at end”. When used in combination with other flags, it seeks to the end of the file immediately after opening it. This can be useful for quickly determining the file size or for efficient file processing.

std::ifstream file;
file.open("data.txt", std::ios::in | std::ios::ate);

Step 3: Check if the file was opened successfully

After opening the file, it’s important to check if the file was opened successfully before proceeding to read from it. We can check this by calling the is_open() method of the stream object.

if (!infile.is_open()) {
    // File opening failed, handle the error
    cout << "Error opening the file." << endl;
    return 1; // or exit the program with an error code
}

Step 4: Read from the file

There are various ways to read from a file using C++ streams. Here are a few examples:

4.1. Reading formatted data using the extraction operator (>>)

We can read formatted data from the text file using the extraction operator, just like we do with cin.

int num;
double total;
string name;

infile >> num >> total >> name;

In this example, the file contains an integer, a double, and a string on separate lines. The extraction operator reads each value from the file and stores it in the corresponding variables.

4.2. Reading an entire line using getline()

If we want to read an entire line of text from the file, we can use the getline() method.

string line;
getline(infile, line);

This reads a single line from the file and stores it in the string variable line. It stops reading when it encounters a newline character \n.

4.3. Reading all lines in the file using a loop

To read all the lines in a text file, we can use a loop and repeatedly call getline() until we reach the end of the file (EOF).

string line;
while (getline(infile, line)) {
    // Process the line
    cout << line << endl;
}

This loop reads each line from the file and processes it. It continues until the getline() function returns false, indicating that we have reached the end of the file.

Alternatively, we can use the get() method to fetch the file contents character by character. This produces the same output as getline() when the starting and ending points of the parsing are the same for both. get() is illustrated in the following snippet:

char c;
while (infile.get(c)) {
        cout << c;
}

Step 5: Close the file

Once we are done reading from the file, it’s important to close it using the close() method.

infile.close();

Closing the file releases any resources associated with it and ensures that the file is properly closed.

Here’s a complete example that incorporates the above steps:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    ifstream infile("filename.txt");

    if (!infile.is_open()) {
        cout << "Error opening the file." << endl;
        return 1;
    }

    int num;
    double total;
    string name;

    infile >> num >> total >> name;
    cout << "Num: " << num << endl;
    cout << "Total: " << total << endl;
    cout << "Name: " << name << endl;

    string line;
    while (getline(infile, line)) {
        cout << line << endl;
    }

    infile.close();

    return 0;
}

A sample filename.txt for the above example would be as follows:

100
255.67
Larry
This is a line of text.
Another line here.
And a final line.

The output of the program for the above sample would be as follows:

Num: 100
Total: 255.67
Name: Larry

This is a line of text.
Another line here.
And a final line.

The ofstream class

The ofstream class in C++ is a derived class of the ostream class and is specifically designed for writing output to files. It provides a convenient interface to create and write data to text files, allowing programmers to perform various operations such as writing lines, formatted output, or individual characters to a file.

With the ofstream class, you can easily open a file for output, write data to it, and perform output operations just like you would with the standard output stream cout. It supports output operations such as writing data using the insertion operator <<, writing lines with endl, or writing individual characters using member functions like put.

Couple of things to keep in mind regarding the writing to a file operations:

  1. Output files will be created if they do not exist.
  2. Output files will be overwritten (truncated) by default.
  3. Can be opened so that new writes append.
  4. Can be opened in text or binary formats.

Here’s an example that demonstrates the usage of ofstream for writing text to a file:

#include <iostream>
#include <fstream>

int main() {
  std::ofstream outfile("output.txt"); // Alternatively, .open() method can be used.

  if (outfile.is_open()) {
    outfile << "Hello, world!" << std::endl;
    outfile << "This is a sample line." << std::endl;
    outfile.close();
    std::cout << "Data written to the file successfully." << std::endl;
  } else {
    std::cout << "Failed to create the file." << std::endl;
  }

  return 0;
}

In this example, we create an ofstream object named outfile and open the file output.txt for writing. We then use the insertion operator << to write text to the file. Finally, we close the file using the close() member function. If the file was created and written successfully, it displays a success message on the console.

Similar to ifstream, ofstream also supports different flags and modes to control the behaviour of the file stream. These flags and modes determine how the file is opened and how data is written to it. Here are some commonly used flags and modes:

  • std::ios::out (default): This flag indicates that the file should be opened for output. It allows you to write data to the file.
  • std::ios::app: This flag is used to open the file in append mode. When the file is opened in this mode, any data written to it will be appended to the existing contents of the file, rather than overwriting them.
  • std::ios::trunc: This flag is used to truncate the file if it already exists. If the file is opened with this flag, its existing contents will be deleted, and the file will be treated as empty before writing new data to it.
  • std::ios::binary: This flag is used to open the file in binary mode. When the file is opened in binary mode, data is treated as binary data rather than text. This mode is useful when working with non-text files or when you want to perform low-level operations on the file.

These flags can be combined using the bitwise OR operator (|) to specify multiple flags simultaneously. Here are a few examples:

#include <iostream>
#include <fstream>

int main() {
  std::ofstream outfile;

  // Open the file in output mode (default)
  outfile.open("output.txt");

  // Open the file in append mode
  outfile.open("output.txt", std::ios::app);

  // Open the file in truncation mode
  outfile.open("output.txt", std::ios::trunc);

  // Open the file in binary mode
  outfile.open("output.txt", std::ios::binary);

  return 0;
}

The fstream class

The fstream class in C++ provides a convenient way to work with files for both input and output operations. It is derived from the iostream class and combines the functionalities of both ifstream and ofstream. With fstream, you can perform read and write operations on files using the same stream object. Here’s an overview of fstream along with some examples and modes:

Opening Files

To open a file using fstream, you need to declare an fstream object and use the open() member function to specify the file name and open mode. The open modes determine how the file will be accessed. Here are the commonly used modes:

  • std::ios::in: Open the file for reading.
  • std::ios::out: Open the file for writing.
  • std::ios::app: Append to the end of the file.
  • std::ios::trunc: Truncate the file if it exists.
  • std::ios::binary: Open the file in binary mode.

You can combine multiple modes using the bitwise OR operator (|). If no mode is specified, std::ios::in | std::ios::out is used by default. Here’s an example:

#include <iostream>
#include <fstream>

int main() {
  std::fstream file;
  
  // Open file in write mode and truncate if it exists
  file.open("data.txt", std::ios::out | std::ios::trunc);
  
  // Check if file opened successfully
  if (file.is_open()) {
    // File operations
    file << "Hello, World!";
    
    // Close the file
    file.close();
  }
  return 0;
}

In this example, an fstream object named file is created. The file data.txt is opened in write mode with std::ios::out | std::ios::trunc. The file is then written with the text “Hello, World!” using the stream insertion operator (<<). Finally, the file is closed with the close() member function.

Reading from Files

To read from a file using fstream, you can use the stream extraction operator (>>) or the getline() function to extract data from the file. Here’s an example:

#include <iostream>
#include <fstream>
#include <string>

int main() {
  std::fstream file;
  std::string line;
  
  // Open file in read mode
  file.open("data.txt", std::ios::in);
  
  // Check if file opened successfully
  if (file.is_open()) {
    // Read file line by line
    while (getline(file, line)) {
      std::cout << line << std::endl;
    }
    
    // Close the file
    file.close();
  }
  
  return 0;
}

In this example, the file data.txt is opened in read mode with std::ios::in. The getline() function is used to read the file line by line, and each line is printed to the console.

Random Access

One of the advantages of fstream is the ability to perform random access operations on files. You can use the seekg() (read pointer seek) and seekp() (write pointer seek) member functions to set the position of the read and write pointers within the file, respectively. Here’s an example:

#include <iostream>
#include <fstream>

int main() {
  std::fstream file;
  int num;
  
  // Open file in read/write mode
  file.open("data.txt", std::ios::in | std::ios::out);
  
  // Check if file opened successfully
  if (file.is_open()) {
    // Read and print the first number
    file >> num;
    std::cout << "First number: " << num << std::endl;
    
    // Set the write pointer to the beginning of the file
    file.seekp(0, std::ios::beg);
    
    // Write a new number
    file << 42;
    
    // Close the file
    file.close();
  }
  
  return 0;
}

In this example, the file data.txt is opened in read/write mode with std::ios::in | std::ios::out. The first number is read from the file and printed. Then, the seekp() function is used to set the write pointer to the beginning of the file, and the number 42 is written to the file, overwriting the existing data.

Exercises

  1. Write a program that reads a text file named “input.txt” and prints its contents to the console.
  2. Write a program that reads a text file named “input.txt” and counts the number of lines in the file.
  3. Write a program that prompts the user to enter a sentence and saves it to a text file named “output.txt”.
  4. Write a program that reads a binary file named “data.bin” and calculates the sum of all the integers in the file.
  5. Write a program that creates a text file named “output.txt” and writes the numbers from 1 to 100, each on a new line.
  6. Write a program that reads a text file named “input.txt” and creates a new file named “output.txt” with all the vowels removed from the text.
  7. Write a program that reads a binary file named “data.bin” and finds the maximum and minimum values in the file.
  8. Write a program that reads a text file named “input.txt” and writes its contents in reverse order to a new file named “output.txt”.
  9. Write a program that reads a text file named “input.txt” and counts the occurrences of a specific word entered by the user.
  10. Write a program that reads a text file named “input.txt” and capitalise the first letter of each word, then saves the modified text to a new file named “output.txt”.

String streams

String Streams provide a convenient way to manipulate strings as if they were streams of input or output. This concept is powerful for tasks like data validation. To work with string streams, we need to include the <sstream> header file. There are three classes we can use: stringstream, istringstream, and ostringstream.

Here’s an example of reading from a stringstream:

#include <sstream>
#include <iostream>

int main() {
  std::string info = "Moe 100 1234.5";
  std::istringstream iss(info);

  std::string name;
  int num;
  double total;

  iss >> name >> num >> total;

  std::cout << "Name: " << name << std::endl;
  std::cout << "Number: " << num << std::endl;
  std::cout << "Total: " << total << std::endl;

  return 0;
}

Output:

Name: Moe
Number: 100
Total: 1234.5

In this example, we initialise the istringstream object iss with the string info. We then use the stream extraction operator (>>) to read the data from the stringstream into the respective variables name, num, and total. Finally, we print the values to the console.

Now let’s look at an example of writing to a stringstream:

#include <sstream>
#include <iostream>

int main() {
  int num = 100;
  double total = 1234.5;
  std::string name = "Moe";

  std::ostringstream oss;
  oss << "Name: " << name << ", Number: " << num << ", Total: " << total;

  std::cout << "Formatted string: " << oss.str() << std::endl;

  return 0;
}

Output:

Formatted string: Name: Moe, Number: 100, Total: 1234.5

In this example, we use the ostringstream object oss to write formatted data to the stringstream. We use the stream insertion operator (<<) to insert the data into the stringstream. The resulting formatted string can be retrieved using the str() method of the stringstream.

Lastly, let’s explore an example of basic data validation using a stringstream:

#include <sstream>
#include <iostream>

int main() {
  std::string input;
  int value;
  bool done = false;

  do {
    std::cout << "Enter an integer: ";
    std::getline(std::cin, input);

    std::istringstream iss(input);
    if (iss >> value) {
      done = true;
    } else {
      std::cout << "Invalid input. Please enter an integer." << std::endl;
    }

    // Clear the input stream and ignore any remaining characters
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // ignore any remaining characters in the input stream until a newline character ('\n') is encountered
  } while (!done);

  std::cout << "Entered integer: " << value << std::endl;

  return 0;
}

In this example, we repeatedly prompt the user to enter an integer until a valid integer is provided. We use a do-while loop to ensure that the input is validated at least once. Inside the loop, we read the user’s input into the string variable input. Then, we create an istringstream object iss and connect it to input. We attempt to extract an integer from iss using the stream extraction operator (>>). If the extraction is successful, we set done to true and exit the loop.

Exercises

Exercise 1: Word Count

Write a program that takes a sentence as input and counts the number of words in it. Use a string stream to split the sentence into individual words.

Exercise 2: String Reversal

Write a program that takes a string as input and reverses its content using string streams.

Exercise 3: Palindrome Check

Write a program that checks if a given string is a palindrome. Use string streams to remove any spaces or punctuation marks from the input string and compare the resulting string with its reverse.

Exercise 4: CSV Parsing

Write a program that reads a CSV (Comma-Separated Values) file and extracts specific data from it. Use string streams to parse the CSV rows and extract the desired values.

Exercise 5: Data Serialization

Write a program that reads data from a file, stores it in a data structure (e.g., a vector or a map), and then serialise the data back into a string using string streams.

Leave a Reply

Your email address will not be published. Required fields are marked *

You cannot copy content of this page