Is it Possible to write and read a struct from a vector to a file?

Hi guys, I'm slowly getting back into programming and I'm getting stuck on a small project of mine.

I would like to have a text file from which I can read and Write. I want to either read from it and store data to a vector or write the content of a vector to it.

The only issue I'm having is that the vector contains int, bool, and string variables.

My question is : is that possible, and if it is, how would you simply do it?


The structure I'm using looks something like this :
1
2
3
4
5
6
struct Test
{
    int number;
    bool flag;
    string text;
};



And I would like to store data to a vector by reading from a file
1
2
fstream file;
vector<Test> vec;


I know that i can convert each of the three variables into char arrays for instance to write and read more easily on/from the file. But is there any more practical way of keeping them as they are?


Thanks so much in advance, any help is much appreciated.

Have a good day / evening / nigh.
Last edited on
Well, what do you want to have in the file?

If you want a "human readable" text file (e.g. CSV), then there will be no other way than iterating the vector, convert each of the three fields of each Test element into text, and write that text into the output file.

...either that, or use some sort of "serialization" library.


Of course, you can always just dump your Test elements into the file as "binary" data, if you want to:

1
2
3
4
5
6
std::fstream file;
file.open("test.bin", std::ios::out | std::ios::trunc | std::ios::binary);
for (vector<Test>::const_iterator iter = vec.cbegin(); iter != vec.cend(); iter++)
{
    file.write(reinterpret_cast<char*>(&(*iter)), sizeof(Test));
}

But then you should also change your struct like this, to ensure a fixed size in a platform-independent way:
1
2
3
4
5
6
7
8
9
#include <stdint.h>
#define MAX_STRLEN 256U

struct Test
{
    int32_t number;
    uint8_t flag;
    char text[MAX_STRLEN];
};

Also note that you may need to deal with byte order (LE vs. BE) when exchanging data between platforms!

(Note: We use a fixed-size char-array to store the string here, to ensure that each record has a fixed size. If you want to store your strings with "true" variable length, then some more work will be required; it could be achieved with a "flexible array member", but then writing/reading the struct will not be simple anymore!)
Last edited on
this is the easiest way to do it for a simple text file as requested. I have overloaded the stream operator for your struct, which then works with both cout and file out using << ... I leave it for you to play with getting >> to work (probably not friendly for cin but useful for file reading).
then you can just iterate the file or container to read/write a bunch of them, but get it working for just 1 record first, then vector it up, little steps testing along the way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

struct Test
{
    int number;
    bool flag;
    string text;
	friend ostream& operator<<(ostream& os, const Test &t);
};

ostream& operator<<(ostream& os, const Test &t)
{
	string bs[]={"false","true"};
	os<< std::to_string(t.number) << " " << bs[t.flag] << " " << t.text << endl;
	return os;	
}

int main()
{
   ofstream ofs("tst.txt");	
   Test t{42,true,"hoopajoo for you"};
   cout << t;
   ofs << t;      
}



C:\c>a
42 true hoopajoo for you

C:\c>cat tst.txt
42 true hoopajoo for you


* I think there is a way to stringify bools in the language, but I don't recall it. I prefer just 0/1 int output for them.
Last edited on
jonnin wrote:
I think there is a way to stringify bools in the language

Insert std::boolalpha into the output stream before inserting the Boolean variable.

Or let C++20's <format> create a string that encodes a Boolean true/false value.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <format>

int main()
{
   std::cout << false << ", " << true << '\n';

   std::cout << std::boolalpha;

   std::cout << false << ", " << true << '\n';

   std::cout << std::format("{}, {}\n", false, true);
}
0, 1
false, true
false, true
Thanks to the three of you, i will try both of what you mentioned above and let you know, I think jonnin's approach is the most suitable in my case since I'm using the text file more as a database, than a readeable file, my bad for not making that clearer from the get go.

basically what i want my program to do is the following (considering my struct is what contains data)

-> create a vector of struct
-> read file and store existing data from file to vector
-> somehow display contents of vector (whole vector or via user keyword search for instance)
-> add new data to the vector and append it to text file so that next time the program is run, data is saved on there
-> also delete data from file later on

I will try and re-write a test program to just write and read on/from the file to the vector and i'll let you know.

Thanks for your input guys 0/
There's no real need to write to the file true/false for boolean. That just makes reading more complicated. Just write as 1/0 to simplify reading. Perhaps:

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
#include <string>
#include <fstream>
#include <iostream>

struct Test {
	int number {};
	bool flag {};
	std::string text;

	friend std::ostream& operator<<(std::ostream& os, const Test& t) {
		return os << t.number << ' ' << t.flag << ' ' << t.text << '\n';
	}

	friend std::istream& operator>>(std::istream& is, Test& t) {
		return std::getline(is >> t.number >> t.flag >> std::ws, t.text);
	}
};

int main() {
	{
		std::ofstream ofs("tst.txt");

		Test t { 42, true, "hoopajoo for you" };

		std::cout << t;
		ofs << t;
	}

	{
		std::ifstream ifs("tst.txt");

		Test t {};

		ifs >> t;
		std::cout << t;
	}
}



42 1 hoopajoo for you
42 1 hoopajoo for you

Last edited on
If you want to store your elements (structs) in textual form, consider using a CSV library:
https://github.com/d99kris/rapidcsv
https://github.com/ben-strasser/fast-cpp-csv-parser
For this example, isn't using a 3rd-party csv library a bit of an overkill?
Well, consider that you have to take care of escaping separator characters in your strings and things like that. Also, as soon as you also need to read-in your data again and deal with all sorts of possible error cases...

(You'd quickly end up writing your own CSV writer/parser library, so why not pick an existing one?)
Last edited on
I quickly managed to write a test program to try and perform the task I mentioned above:

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
#include <iostream>
#include <fstream>
#include <vector>

using namespace std;

struct Test
{
	int number;
	char name[200];
	bool flag;

	void display()
	{
		cout << "\n\n\tStruct contains :"
			<< "\nName: \t\t\t\t" << name
			<< "\nNumber: \t\t\t" << number
			<< "\nFlag (0 = false, 1 = true): \t" << flag
			<< "\n\n";

		return;
	}
};

Test getInput()
{
	Test t;

	cout << "\n\nEnter a number: ";
	cin >> t.number;
	cin.ignore();
	cout << "Enter a name: ";
	cin.getline(t.name, 200);
	cout << "Enter flag (either 0 or 1): ";
	cin >> t.flag;

	return t;
}

int main()
{
	Test tempStruct;
	vector<Test> vec;
	fstream file;
	string fileName = "file.txt";

     
	for (int count = 0; count < 3; count++)			                                            //input twice
	{
		tempStruct = getInput();							            //get user input
		vec.push_back(tempStruct);						              	    //push back struct into vector
	}

	//open file for writing
	file.open(fileName, ios::out | ios::trunc | ios::binary);

	for (int count = 0; count < vec.size(); count++)		                                    //write contents of vector on file
	{
		file.write(reinterpret_cast<char*>(&vec[count]), sizeof(vec[count]));
	}
	//close file 
	file.close();

	//clear contents of vector
	vec.clear();

	//open file for reading
	file.open(fileName, ios::in | ios::beg | ios::binary);

	while (file.read(reinterpret_cast<char*>(&tempStruct), sizeof(tempStruct)))	                    //read data from file into vector
	{
		vec.push_back(tempStruct);
	}
	
	//close file
	file.close();


	for (int count = 0; count < vec.size(); count++)	                                            //display contents of vector
	{
		vec[count].display();
	}


	system("PAUSE");

	return(0);
}

Enter a number: 4
Enter a name: Paul
Enter a flag (either 0 or 1): 1

Enter a number: 789
Enter a name: Jack
Enter a flag (either 0 or 1): 0

Enter a number: 96
Enter a name: Will Smoth
Enter a flag (either 0 or 1): 1

    Struct contains :
Name:                         Paul
Number:                       4
Flag (0 = false, 1 = true):   1

Name:                         Jack
Number:                       789
Flag (0 = false, 1 = true):   0

Name:                         Will Smoth
Number:                       96
Flag (0 = false, 1 = true):   1


The only thing that is going to differ in the final version of my project is that I want to keep existing data on file every time I open it and only wipe it when the user decides so.

Thank you for the suggestion about using an external lib but I wanted to try and do it without using such :)

Edit: Feel free to criticise my code, I'm a bit rusty..
Last edited on
Have the number of chars in name a global const - or have getInput() part of the struct when the const can then be local. Perhaps:

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
#include <iostream>
#include <fstream>
#include <vector>
#include <iomanip>

struct Test {
	static constexpr size_t NoNam { 200 };

	int number {};
	char name[NoNam] {};
	bool flag {};

	void display() const {
		std::cout << "\n\n\tStruct contains :"
			<< "\nName: \t\t\t" << name
			<< "\nNumber: \t\t" << number
			<< "\nFlag: \t\t\t" << std::boolalpha << flag
			<< "\n\n";
	}

	Test& getInput() {
		std::cout << "\n\nEnter a number: ";
		std::cin >> number;
		std::cin.ignore();
		std::cout << "Enter a name: ";
		std::cin.getline(name, 200);
		std::cout << "Enter flag (either 0 or 1): ";
		std::cin >> flag;

		return *this;
	}
};

int main() {
	const std::string fileName { "file.txt" };
	std::vector<Test> vec;

	for (int count {}; count < 3; ++count) {
		Test tempStruct;

		vec.push_back(tempStruct.getInput());
	}

	std::fstream file(fileName, std::ios::out | std::ios::trunc | std::ios::binary);

	for (auto& v : vec)
		file.write(reinterpret_cast<char*>(&v), sizeof(v));

	file.close();
	vec.clear();

	file.open(fileName, std::ios::in | std::ios::binary);

	for (Test t; file.read(reinterpret_cast<char*>(&t), sizeof(t)); )
		vec.push_back(t);

	file.close();

	for (const auto& v : vec)
		v.display();
}




Enter a number: 123
Enter a name: err trr
Enter flag (either 0 or 1): 0


Enter a number: 4545
Enter a name: eer rr
Enter flag (either 0 or 1): 1


Enter a number: 6678
Enter a name: ff ff
Enter flag (either 0 or 1): 0


        Struct contains :
Name:                   err trr
Number:                 123
Flag:                   false



        Struct contains :
Name:                   eer rr
Number:                 4545
Flag:                   true



        Struct contains :
Name:                   ff ff
Number:                 6678
Flag:                   false

Thank you very much for all of your feedback, I have it working now!
Have a great day all, take care :)
Topic archived. No new replies allowed.