Binary I/O in C++

The ios::binary mode can be used to open a file for binary input and output.

So far, you have used text files. Files can be classified into text and binary. A file that can be processed (read, created, or modified) using a text editor such as Notepad on Windows or vi on UNIX is called a text file. All the other files are called binary files. You cannot read binary files using a text editor. They are designed to be read by programs. For example, the C++ source programs are stored in text files and can be read by a text editor, but the C++ execut­able files are stored in binary files and are read by the operating system.

Although it is not technically precise and correct, you can envision a text file as consisting of a sequence of characters and a binary file as consisting of a sequence of bits. For example, the decimal integer 199 is stored as the sequence of the three characters, ‘1’, ‘9’, ‘9’, in a text file, and the same integer is stored as an integer C7 in a binary file, because decimal 199 equals hex C7 (199 = 12 X 161 + 7). The advantage of binary files is that they are more efficient to process than text files.

Binary I/O does not require conversions. If you write a numeric value to a file using binary I/O, the exact value in the memory is copied into the file. To perform binary I/O in C++, you have ios::binary to open a file using the binary mode ios::bi nary. By default, a file is opened in text mode.

You used the << operator and put function to write data to a text file and the >> operator, get, and getline functions to read data from a text file. To read/write data from/to a binary file, you have to use the read and write functions on a stream.

1. The write Function

The syntax for the write function is

streamObject.write(const char* s, int size)

which writes an array of bytes in the type char*. Each character is a byte.

Listing 13.10 shows an example of using the write function.

Listing 13.10 BinaryCharOutput.cpp

1 #include <iostream>
2
#include <fstream>
3
#include <string>
4
using namespace std;
5
6
int main()
7 {
8     fstream binaryio(
“city.dat”, ios::out | ios::binary);
9     string s =
“Atlanta”;
10    binaryio.write(s.c_str(), s.size());
// Write s to file
11    binaryio.close();
12

13    cout << “Done” << endl;
14
15   
return 0;
16 }

Line 8 opens the binary file city.dat for output. Invoking binaryio.write(s.c_str(), s.size()) (line 10) writes string s to the file.

Often you need to write data other than characters. How can you accomplish this? You can use the reinterpret_cast operator. The reinterpret_cast operator can cast any pointer type to another pointer type of unrelated classes. It simply performs a binary copy of the value from one type to the other without altering the data. The syntax of using the reinterpret_cast operator is as follows:

reinterpret_cast<dataType*>(address) 

Here, address is the starting address of the data (primitive, array, or object) and dataType is the data type you are casting to. In this case for binary I/O, it is char*.

For example, see the code in Listing 13.11.

Listing I3.II BinaryIntOutput.cpp

1 #include <iostream>
2
#include <fstream>
3
using namespace std;
4
5
int main()
6 {
7    fstream binaryio(
“temp.dat”, ios::out | ios::binary);
8   
int value = 199;
9    binaryio.write(
reinterpret_cast<char*>(&value), sizeof(value));
10   binaryio.close();
11
12   cout <<
“Done” << endl;
13
14   
return 0;
15 }

Line 9 writes the content in variable value to the file. reinterpret_cast<char*>(&value) cast the address of the int value to the type char*. sizeof(value) returns the storage size for the value variable, which is 4, since it is an int type variable.

2. The read Function

The syntax for the read function is

streamObject.read(char* address, int size)       

The size parameter indicates the maximum number of bytes read. The actual number of bytes read can be obtained from a member function gcount.

Assume the file city.dat was created in Listing 13.10. Listing 13.12 reads the bytes using the read function.

Listing 13.12 BinaryCharInput.cpp

1 #include <iostream>
2
#include <fstream>
3
using namespace std;
4
5
int main()
6 {
7    fstream binaryio(
“city.dat”, ios::in | ios::binary);
8   
char s[10]; // Array of 10 bytes. Each character is a byte.
9    binaryio.read(s, 10);
10    cout <<
“Number of chars read: ” << binaryio.gcount() << endl;
11    s[binaryio.gcount()] =
‘\0’; // Append a C-string terminator
12    cout << s << endl;
13    binaryio.close();
14
15   
return 0;
16 }

Line 7 opens the binary file city.dat for input. Invoking binaryio.read(s, 10) (line 9) reads up to 10 bytes from the file to the array. The actual number of bytes read can be deter­mined by invoking binaryio.gcount() (line 11).

Assume that the file temp.dat was created in Listing 13.11. Listing 13.13 reads the integer using the read function.

Listing 13.13 BinaryIntInput.cpp

1 #include <iostream>
2
#include <fstream>
3
using namespace std;
4
5
int main()
6 {
7    fstream binaryio(
“temp.dat”, ios::in | ios::binary);
8   
int value;
9    binaryio.read(
reinterpret_cast<char*>(&value), sizeof(value));
10   cout << value << endl;
11   binaryio.close();
12
13   
return 0;
14 }

The data in the file temp.dat were created in Listing 13.11. The data consisted of an integer and were cast to bytes before stored. This program first read the data as bytes and then used the reinterpret_cast operator to cast bytes into an int value (line 9).

3. Example: Binary Array I/O

You can use the reinterpret_cast operator to cast data of any type to bytes and vice versa. This section gives an example in Listing 13.14 to write an array of double values to a binary file and read it back from the file.

Listing 13.14 BinaryArraylO.cpp

1 #include <iostream>
2
#include <fstream>
3
using namespace std;
4
5
int main()
6 {
7   
const int SIZE = 5; // Array size
8
9    fstream binaryio;
// Create stream object
10
11   
// Write array to the file
12   binaryio.open(“array.dat”, ios::out | ios::binary);
13   
double array[SIZE] = {3.4, 1.3, 2.5, 5.66, 6.9};
14   binaryio.write(
reinterpret_cast<char*>(&array), sizeof(array));
15   binaryio.close();
16
17   
// Read array from the file
18   binaryio.open(“array.dat”, ios::in | ios::binary);
19   
double result[SIZE];
20   binaryio.read(
reinterpret_cast<char*>(&result), sizeof(result));
21   binaryio.close();
22
23   
// Display array
24    for (int i = 0; i < SIZE; i++)
25    cout << result[i] <<
” “;
26
27   
return 0;
28 }

The program creates a stream object in line 9, opens the file array.dat for binary output in line 12, writes an array of double values to the file in line 14, and closes the file in line 15.

The program then opens the file array.dat for binary input in line 18, reads an array of double values from the file in line 20, and closes the file in line 21.

Finally, the program displays the contents in the array result (lines 24–25)

4. Example: Binary Object I/O

This section gives an example of writing objects to a binary file and reading the objects back from the file.

Listing 13.1 writes student records into a text file. A student record consists of first name, middle initial, last name, and score. These fields are written to the file separately. A better way of processing is to define a class to model records. Each record is an object of the Student class.

Let the class be named Student with the data fields firstName, mi, lastName, and score, their supporting accessors and mutators, and two constructors. The class UML diagram is shown in Figure 13.4

Listing 13.15 defines the Student class in the header file, and Listing 13.16 implements the class. Note that the first name and last name are stored in two arrays of characters with a fixed-length 25 internally (lines 22, 24), so that every student record will have the same size. This is necessary to ensure that students can be read from the file correctly. Since it is easier to use the string type than C-string, the string type is used in the get and set functions for firstName and lastName (lines 12, 14, 16, 18).

Listing 13.15 Student.h

1 #ifndef STUDENT_H
2
#define STUDENT_H
3
#include <string>
4
using namespace std;
5
6
class Student
7 {
8
public:
9    Student();
10    Student(
const string& firstName, char mi,
11   
const string& lastName, int score);
12   
void setFirstName(const string& s);
13   
void setMi(char mi);
14   
void setLastName(const string& s);
15   
void setScore(int score);
16    string getFirstName()
const;
17   
char getMi() const;
18    string getLastName()
const;
19   
int getScore() const;
20
21   
private:
22   
char firstName[25];
23   
char mi;
24   
char lastName[25];
25   
int score;
26 };
27
28
#endif

Listing 13.16 Student.cpp

1 #include “Student.h”
2 #include <cstring>
3
4
// Construct a default student
5 Student::Student()
6 {
7 }
8
9
// Construct a Student object with specified data
10 Student::Student(const string& firstName, char mi,
11
const string& lastName, int score)
12 {
13    setFirstName(firstName);
14    setMi(mi);
15    setLastName(lastName);
16    setScore(score);
17 }
18
19
void Student::setFirstName(const string& s)
20 {
21    strcpy(firstName, s.c_str());
22 }
23
24
void Student::setMi(char mi)
25 {
26   
this->mi = mi;
27 }
28
29
void Student::setLastName(const string& s)
30 {
31    strcpy(lastName, s.c_str());
32 }
33
34
void Student::setScore(int score)
35 {
36   
this->score = score;
37 }
38
39 string Student::getFirstName()
const
40 {
41   
return string(firstName);
42 }
43
44
char Student::getMi() const
45 {
46   
return mi;
47 }
48
49 string Student::getLastName()
const
50 {
51   
return string(lastName);
52 }
53
54
int Student::getScore() const
55 {
56   
return score;
57 }

Listing 13.17 BinaryObjectIO.cpp

1 #include <iostream>
2
#include <fstream>
3
#include “Student.h”
4 using namespace std;
5
6
void displayStudent(const Student& student)
7 {
8     cout << student.getFirstName() <<
” “;
9     cout << student.getMi() <<
” “;
10    cout << student.getLastName() <<
” “;
11    cout << student.getScore() << endl;
12 }
13
14
int main()
15 {
16    fstream binaryio;
// Create stream object
17    binaryio.open(“student.dat”, ios::out | ios::binary);
18
19    Student student1(
“John”, ‘T’, “Smith”, 90);
20    Student student2(
“Eric”, ‘K’, “Jones”, 85);
21    Student student3(
“Susan”, ‘T’, “King”, 67);
22    Student student4(
“Kim”, ‘K’, “Peterson”, 95);
23
24    binaryio.write(
reinterpret_cast<char*>
25    (&student1),
sizeof(Student));
26    binaryio.write(
reinterpret_cast<char*>
27    (&student2),
sizeof(Student));
28    binaryio.write(
reinterpret_cast<char*>
29    (&student3),
sizeof(Student));
30    binaryio.write(
reinterpret_cast<char*>
31    (&student4),
sizeof(Student));
32
33     binaryio.close();
34
35   
// Read student back from the file
36    binaryio.open(“student.dat”, ios::in | ios::binary);
37
38    Student studentNew;
39
40    binaryio.read(
reinterpret_cast<char*>
41    (&studentNew),
sizeof(Student));
42
43    displayStudent(studentNew);
44
45    binaryio.read(
reinterpret_cast<char*>
46    (&studentNew),
sizeof(Student));
47
48    displayStudent(studentNew);
49
50    binaryio.close();
51
52   
return 0;
53 }

The program creates a stream object in line 16, opens the file student.dat for binary output in line 17, creates four Student objects in lines 19-22, writes them to the file in lines 24-31, and closes the file in line 33.

The statement to write an object to the file is

binaryio.write(reinterpret_cast<char*>

(&student1), sizeof(Student));

The address of object studentl is cast into the type char*. The size of an object is determined by the data fields in the object. Every student has the same size, which is sizeof(Student).

The program opens the file student.dat for binary input in line 36, creates a Student object using its no-arg constructor in line 38, reads a Student object from the file in lines 40-41, and displays the object’s data in line 43. The program continues to read another object (lines 45-46) and displays its data in line 48.

Finally, the program closes the file in line 50.

Source: Liang Y. Daniel (2013), Introduction to programming with C++, Pearson; 3rd edition.

Leave a Reply

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