Saving a vector of structs into a binary file?

I'm doing a final project for coding class and our program has to be menu driven with file IO in the command prompt. The only problem is saving a vector of structs into a file. I got the part where it saves the User struct (by commenting out the vector), but I don't know how to get it to write and read a vector of structs from a file. We haven't gotten to the part with saving structs to binary files in class yet (we will on Monday). Can anyone help me on how to write/read a vector of structures into/from the people.dat file?


All the file IO functions start on line 232.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/********************************************************
* Project: 
* File: main.cpp
* Authors: 
* Date: 11/19/2014
* Description: 
*********************************************************/
#include <iostream>
#include <vector>
#include <string>
#include <conio.h>
#include <iomanip>
#include <stdio.h>
#include <cstdlib>
#include <cstring>
#include <stdlib.h>
#include <ctime>
#include <time.h>
#include <fstream>


using namespace std;

struct Service
{
	string website;
	string userName;
	string password;
};

struct User
{
	string servicePin;
	string name;
	string phoneNumber;
	vector<Service> services;
};

//prototypes
void readFile();
void writeFile();
void viewInfo();
void addUser();
Service getService();
char genRandom();


static const char alphanum[] = "0123456789";
int stringLength = sizeof(alphanum) - 1;


// Random string generator function.
char genRandom()  
{

    return alphanum[rand() % stringLength];
}

User u1;

int main()
{
	//variables
	int userInput;
	

	cout << "Hello, welcome to Password All-In-One!" << endl;
	cout << endl;
	cout << "Please press 0 if you are a new user." << endl;
	cout << "Please press 1 if you are a returning user." << endl;
	cout << "Please press 2 to quit." << endl;
	
	cin >> userInput;
	cout << endl;

	if (userInput == 0)
	{
		addUser();
	}

	if (userInput == 1)
	{
		//add function(s) here
		readFile();
	}

	if (userInput == 2)
	{
		cout << endl;
		cout << "Bye." << endl;

		return 0;
	}

	cout << endl;

	return 0;
}
char genRandom()  
{

    return alphanum[rand() % stringLength];
}

void addUser()
{
	//variables
   string masterP;
   string name1;
	string number1;
   char charInput = 'y';
	bool pinInput;
	bool pinDoWhile;
	int pinLength;
	string Str;
	

	//request user information
   cout << "Full name: ";
	cin.ignore();
   getline(cin, name1);
   u1.name = name1;

   cout << "Phone number: ";
   cin.ignore();
	getline(cin, number1);
	u1.phoneNumber = number1;

   cout << "Before you add account services, you need" <<
		" a master PIN." << endl;
	cout << "You can either make your own, or we can"
		<< " generate you one" << endl;
	cout << endl;
	cout << "Press 0 - Create your own PIN" << endl;
	cout << "Press 1 - Have the system create you one" << endl;

	cin >> pinInput;
	
	if (pinInput == false)
	{
		cout << "Please enter a number-only PIN you will not forget" << endl;
		cin >> masterP;
		u1.servicePin = masterP;
	}

   if (pinInput == true)
	{
		do
		{
			cout << "How many digits would you like your PIN to be?" << endl;
			cout << "Enter a PIN length (number): " << endl;
			cin >> pinLength;

			for (int i = 0; i < pinLength; ++i)
			{
				Str += genRandom();
			}

			cout << "Your generated PIN is: " << Str << endl;
			cout << "Press 0 to create another PIN" << endl;
			cout << "Press 1 to continue" << endl;
			cin >> pinDoWhile;
			if (pinDoWhile == false)
			{
				Str = "";
			}
			u1.servicePin = Str;
		} while (pinDoWhile != true);
	}

	

	cout << "This is your current information so far: " << endl;
	cout << endl;
	cout << "Name: " << u1.name << endl;
	cout << "Phone number: " << u1.phoneNumber << endl;
	cout << "PIN: " << u1.servicePin << endl;
	cout << "Please write down or remember your PIN so you" << endl;
	cout << "can access your account later!! " << u1.servicePin << endl;
	cout << endl;

		
   while (charInput == 'y' || charInput == 'Y')
    {
		 //line below saves vector of websites into the user struct
		 //when the function is called
		 u1.services.push_back(getService());
       cout << "Add more website credentials? (y/n):";
       cin >> charInput;
       cin.clear();
       cin.ignore(100, '\n');
    } 

	writeFile();
	cout << "Your credntials have been saved." << endl;
	
}

Service getService()
{
	//variables
	string website1;
	string username1;
	string password1;
	Service s1;
	char verify = 'y';

	//ask for services
	cout << "You are now able to add services. Please type carefully." << endl;

	do
	{
		cout << "Website name: ";
		cin >> website1;
		cout << "Username: ";
		cin >> username1;
		cout << "Password: ";
		cin >> password1;
		cout << "Is this information correct for " << website1 << " ?  press y for yes, n for no" << endl;
		cout << "Username: " << username1 << endl;
		cout << "Password: " << password1 << endl;
		cin >> verify;
	} while (verify == 'n');

	s1.website = website1;
	s1.userName = username1;
	s1.password = password1;

	return s1;
}

void writeFile()
{
	fstream file("people.dat", ios::out | ios::binary);

	// Write the contents of the person structure to the file.
	file.write(reinterpret_cast<char *>(&u1),
		sizeof(u1));
	 file.close();
}

void readFile()
{
	fstream file;

	file.open("people.dat", ios::in | ios::binary);

	if (!file)
	{
		cout << "Error opening file. Program aborting.\n";
		exit(0);
	}

	cout << "Here are the people in the file: \n";
   // Read the first record from the file.
   file.read(reinterpret_cast<char *>(&u1),
   sizeof(u1));
	 
	// Display the record.
	 cout << "Name: ";
	 cout << u1.name << endl;
	 cout << "Phone number: ";
	 cout << u1.phoneNumber << endl;
	 cout << "Password: ";
	 cout << u1.servicePin << endl;
	 cout << "services: ";
	 for (int i = 0; i < u1.services.size(); i++)
	 {
		 cout << u1.services[i].website << endl;
		 cout << u1.services[i].userName << endl;
		 cout << u1.services[i].password << endl;
		 cout << endl;
	 }
	file.close(); 
}
Last edited on
file.write(reinterpret_cast<char *>(&u1), sizeof(u1)); 
Terribly wrong. You cannot save non-POD types like that. You need to write proper serialization routines yourself.

For saving variable length data there are two main approaches:
1) Sentiel teminated: you write your data one by one and after last one write a sentiel value. It can be '\0' symbol for strings, stucture in which all fields are set to predefined nonsentual values or something else.
2) Counted. You write amount of data and then write n entries of data. Example: pascal string — first byte is a number of characters in string, then n characters.
I'm confused.
cool, it reminds the project of my first year of university. Reading your code I cannot understand how people would actually prefer a style of wasting a line for just a parenthesis { } as opposed to K&R. It leaves me astounded.

For the rest I agree with Minipaa, you are trying to store a pointer char* instead of the actual data. As Minipaa said, you need some kind of serialization strategy.


Regards,
Dean
Well the truth is, we have not gotten to this topic in class yet, so I'm just looking ahead in my textbook and trying to do what i can from what the examples show.
this is what they did in the book and used the same idea of file.write....

1
2
3
4
5
6
7
8
9
const int NAME_SIZE = 51, ADDR_SIZE = 51, PHONE_SIZE = 14;
struct Info
{
char name[NAME_SIZE];
int age;
char address1[ADDR_SIZE];
char address2[ADDR_SIZE];
char phone[PHONE_SIZE];
};
How should i go about in order to just write structure contents to a file and then have the ability to read the file later and change something?
Hi Skimmer,

A dirty, with no frills, approach would be to store the fields as they are. Assuming they are fixed size (as you posted above):

1
2
3
4
5
6
7
8
9
10
11
 // include <fstream>
Info info;
...

fstream stream("test.txt", ios::out | ios::binary);
stream.write(info.name, NAME_SIZE);
stream.write(reinterpret_cast<char*>(&info.age),sizeof info.age);  // (endianness warning)
stream.write(info.address1, ADDR_SIZE);
stream.write(info.address2, ADDR_SIZE);
stream.write(info.phone, PHONE_SIZE);
stream.close();


And then you perform the opposite to read back the data, following the exact same sequence:
1
2
3
4
5
6
7
8
9
Info info = {0};
fstream stream("test.txt", ios::in | ios::binary);

stream.read(info.name, NAME_SIZE);
stream.read(reinterpret_cast<char*>(&info.age), sizeof info.age);
stream.read(info.address1, ADDR_SIZE);
stream.read(info.address2, ADDR_SIZE);
stream.read(info.phone, PHONE_SIZE);
stream.close();


Now this approach works until you have fixed size items. In case you want to deal with varying length data (such as strings), minipaa already suggested two strategies: delimit your string with some special character (such as '\0') or prepend them with their size. An example for the second case would be:

1
2
3
4
5
 // serialization
char* content = info.name; // your string
size_t content_sz = strlen(content);
stream.write(reinterpret_cast<char*>(&content_sz), sizeof content_sz);
stream.write(content, content_sz);


1
2
3
4
5
6
7
8
 // deserialization
char* content = info.name; // your string
size_t content_sz (0);
stream.read(reinterpret_cast<char*>(&content_sz), sizeof content_sz);
// if you did not allocate the memory yet, you`d allocate it now w/ content = new char[content_sz +1]; 
// otherwise assume content_sz < | content | 
stream.read(content, content_sz);
content[content_sz] = '\0'; // string terminator 


In practise, a general and portable approach to serialization is tricky, and it is common to use external libraries to deal with it.

All the best,
Dean
Topic archived. No new replies allowed.