Saving structs to files

I have this struct and I wish to add info about an undefined no. of albums (meaning I couldn't have an int n where it'd just stop reading). This information would have to be read from and saved in a file. So, if I used the program today, tomorrow I'd still have the data I put in. How would I go about that?

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 <iostream>
#include <fstream>
using namespace std;
struct album{
 char title[100],artist[100];
 int year, price, stock;
}a[100]; int k=0;
    ofstream myfile("example.txt");
    ifstream yourfile("intrare.in");
void read()
{
    k++;
    cin.getline(a[k].title,100);
    cin.getline(a[k].artist,100);
    yourfile>>a[k].year;
    yourfile>>a[k].price;
    yourfile>>a[k].stock;
}
int main()
{

    if (yourfile.is_open())
  {
    while (read())
    {
      myfile<<a[k].title<<endl;
      myfile<<a[k].artist<<endl;
      myfile<<a[k].year<<endl;
      myfile<<a[k].price<<endl;
      myfile<<a[k].stock<<endl;
    }
 yourfile.close();
  }
    myfile.close();
    return 0;
}

(I'm having trouble figuring out the reading from file part)
Last edited on
To add data to an output file without deleting existing data, open the file in append mode, So L8:

 
ofstream myfile("example.txt", ios_base::app);


http://www.cplusplus.com/reference/fstream/ofstream/open/
Last edited on
If you want to save a struct to a file, I highly recommend to use fixed-size integral types, such as int32_t or int64_t (defined in <stdint.h>) in the definition of your struct, instead of the int or long, because the types like int or long can have a different size on different platforms! This also means your struct would suddenly have a different size, which of course would make a proper data exchange impossible...
Last edited on
using struct to make a global variable is very C-ish. C++ usually treats struct and class as type definitions and make variables of those types appropriately in main or functions. Global variables cause trouble as programs get larger, and its usually best to avoid them (including your file streams) altogether.
You must choose / design a file format for storing your data.

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

// In C++ you can forget C-strings
// It’ll make your life 7-bazillion times easier

struct album
{
  std::string title, artist;
  int year, price, stock;
};

// There are options for storing data as TEXT
// The simplest might simply be to take three lines for each record:
//   title
//   artist
//   year price stock

std::ostream & operator << ( std::ostream & outs, const album & A )
{
  return outs
    << A.title << "\n"
    << A.artist << "\n"
    << A.year << " " << A.price << " " << A.stock << "\n";
}

// Such a format makes it really, really easy to read a record:

std::istream & operator >> ( std::istream & ins, album & A )
{
  getline( ins, A.title );
  getline( ins, A.artist );
  ins >> A.year >> A.price >> A.stock;
  ins.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
  return ins;
}

#include <cmath>
#include <fstream>
#include <vector>

int main()
{
  // likewise, you can use a vector instead of an array for sanity
  std::vector <album> albums;
  
  // Reading a file of albums is child’s play now:
  {
    std::ifstream f( "intrare.in" );
    album A;
    while (f >> A) albums.push_back( A );
  }
  
  // Let’s apply a little inflation to all the prices:
  for (album & A : albums)
    A.price = (int)round( A.price * 1.1 );
  
  // And writing a file is just as easy:
  {
    std::ofstream f( "example.txt" );
    for (const album & A : albums)
      f << A;
  }
}
intrare.in
album one
somebody awesome
1990 1150 1
album two
another artist
2001 1499 2

The file format suggested here is nice because it is flexible (album and artist names may be more than 99 characters) but in general saves you space (most album and artist names are less than 99 characters). Using multiple lines makes reading and writing them extra easy — the only character(s) that are forbidden in an artist and album name are newlines.

Another option would be to use a simple CSV-style format, which for your simple case can be done strictly as:

 
#include <iomanip> 
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
std::ostream & operator << ( std::ostream & outs, const album & A )
{
  return outs
    << quoted( A.title  ) << ","
    << quoted( A.artist ) << ","
    << A.year << "," << A.price << "," << A.stock << "\n";
}

std::istream & operator >> ( std::istream & ins, const char c )
{
  if (ins.peek() == c) ins.get();
  else                 ins.setstate( std::ios::failbit );
  return ins;
}

std::istream & operator >> ( std::istream & ins, album & A )
{
  return ins
    >> quoted( A.title  ) >> ','
    >> quoted( A.artist ) >> ','
    >> A.year             >> ','
    >> A.price            >> ','
    >> A.stock            >> '\n';
}
intrare.in
"album one","somebody awesome",1990,1150,1
"album two","another artist",2001,1499,2

You’ll notice the extra helper extraction function I added there to read an expected character (commas or newlines). You could have done this using getline() + istringstream or again used istream::ignore(), but I personally find this simplest and easiest to read.

This particular helper does not deal with whitespace around commas in your CSV, so if you expect a human to manually manipulate it you can fix things by adding it explicity:

29
30
31
32
33
34
35
36
37
std::istream & operator >> ( std::istream & ins, album & A )
{
  return ins
    >> quoted( A.title  ) >> std::ws >> ','
    >> quoted( A.artist ) >> std::ws >> ','
    >> A.year             >> std::ws >> ','
    >> A.price            >> std::ws >> ','
    >> A.stock            >> std::ws; // this eats '\n' too
}

The CSV format does allow newlines in the album and artist names. I don’t think it is worth the problems that CSV adds, though.

Finally, if you must use an array, make sure to name things well and check that you don’t overflow bounds:

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
#include <ciso646>
#include <cmath>
#include <fstream>

int main()
{
  const int MAX_ALBUMS = 100;
  album albums[MAX_ALBUMS];
  int album_count = 0;

  // Reading a file of albums is child’s play now:
  {
    std::ifstream f( "intrare.in" );
    while ((album_count < MAX_ALBUMS) and (f >> albums[album_count]))
      album_count += 1;
  }

  // Let’s apply a little inflation to all the prices:
  for (int n = 0;  n < album_count;  n += 1)
    albums[n].price = (int)round( albums[n].price * 1.1 );

  // And writing a file is just as easy:
  {
    std::ofstream f( "example.txt" );
    for (int n = 0;  n < album_count;  n += 1)
      f << albums[n];
  }
}

If you with to write your data in a binary format that is a different thing. Let me know and I’ll run down the (good) options with that.
Last edited on
If you go with binary, I prefer the fixed size if you can nail down a reasonable maximum. The fixed size approach wins twice ... once for being able to read and write the struct in one block and again for being able to jump around in the file to the Nth record (literally, in this case).
Last edited on
Topic archived. No new replies allowed.