Read in from file into structure - c++

I want to read in from txt file into structure using fstream.
I save the data to the file in the way shown below:
To read the data i tried some cheeky stuff with getlines or tabsin<
struct tab{
int type,use;
string name, brand;
};
tab tabs[500];
ofstream tabsout;
tabsout.open("tab.txt", ios::out);
for (int i = 0; i < 500; i++){
if (tabs[i].use==1){
tabsout << tabs[i].type << " " << tabs[i].name << " " << tabs[i].brand << "\n";
}
}
tabsout.close();
//input part that fails me :(
int i=0;
ifstream tabsin;
tabsin.open("tab.txt", ios::in);
if (tabsin.is_open()){
while(tabsin.eof() == false)
{
tabsin >> tabs[i].type>>tabs[i].name>>tabs[i].brand;
i++
}
tabsin.close();

You usually want to overload operator>> and operator<< for the class/struct, and put the reading/writing code there:
struct tab{
int type,use;
string name, brand;
friend std::istream &operator>>(std::istream &is, tab &t) {
return is >> t.type >> t.name >> t.brand;
}
friend std::ostream &operator<<(std::ostream &os, tab const &t) {
return os << t.type << " " << t.name << " " << t.brand;
}
};
Then you can read in a file of objects like:
std::ifstream tabsin("tab.txt");
std::vector<tab> tabs{std::istream_iterator<tab>(tabsin),
std::istream_iterator<tab>()};
....and write out the objects like:
for (auto const &t : tabs)
tabsout << t << "\n";
Note that (like any sane C++ programmer) I've used a vector instead of an array, to (among other things) allow storing an arbitrary number of items, and automatically track how many are actually being stored.

For starters, do not use .eof() to control your loop: it doesn't work. Instead, use the stream's state after reading:
int type;
std::string name, brand;
while (in >> type >> name >> brand) {
tabs.push_back(tab(type, name, brand));
}
If your name or brand contain spaces, the above won't work and you will need to write a format where you can know when to stop abd read correspondingly, e.g., using std::getline().
You might also consider wrapping the logic to read or write an object by suitable operators.

istream& getline (istream& is, string& str, char delim);
Take a look at the third parameter, you can use std::getline to parse your line. But that is definitely not the best way to serialize objects. Instead of using a text file, you should use a byte stream.

Related

Reading specific words from a file and storing them in an object

I'm having trouble coding and conceptualizing this project I was assigned. I've looked around for answers to this issue but had little to no luck, maybe it's really obvious. I'm supposed to prompt the user to a filename, the file is assume to have the following format:
Animal:
Name: [value]
Noise: [value]
Legs: [value]
(with no spaces in between)
It should be able to read as many "animal objects" as there are in the file and store them in an animal object class that has 3 parameters (name, noise, legs).
My issue is mostly during the reading in of the file, I can't figure out a good method for reading the file AND storing the information. Here is the code I currently have. Any help with the code I currently have and ideas for storing the values. Sorry if I explained anything poorly, please ask to clarify if I did, thank you in advance.
cout << "Enter the file name: ";
string fileName;
getline(cin, fileName);
cout << endl;
try
{
ifstream animalFile(fileName);
if (!animalFile.good()) // if it's no good, let the user know and let the loop continue to try again
{
cout << "There was a problem with the file " << fileName << endl << endl;
continue;
}
string line;
while (animalFile >> line) // To get you all the lines.
{
getline(animalFile, line); // Saves the line in STRING.
cout << line << endl; // Prints our STRING.
}
}
catch (...)
{
cout << "There was a problem with the file " << fileName << endl << endl;
}
If you're really binded with this file format, consider doing the following to read the data and store it:
#1. Define a class Animal to represent an animal:
struct Animal
{
std::string name;
int legs;
int noise;
}
#2. Define an istream& operator >> (istream&, Animal&) to read one object of this type and check for the correctness of the input.
std::istream& operator >> (std::istream& lhs, Animal& rhs)
{
std::string buf;
lhs >> buf >> buf >> rhs.name >> buf >> rhs.noise >> buf >> rhs.legs;
}
#3. Use std::copy and std::istream_iterator to read all the values from the file to std::vector:
std::istream_iterator<Animal> eos;
std::istream_iterator<Animal> bos(animalFile);
std::vector<Animal> zoo;
std::copy(bos, eos, std::back_inserter(zoo));
This code has no checking for input errors, which may easily be added into istream& operator >> (istream&, Animal&).

C++ structures/arrays [duplicate]

This question already has answers here:
Why does std::getline() skip input after a formatted extraction?
(5 answers)
Closed 9 years ago.
I'm creating a program that stores basic data. After inputing data for the first 'person', the program simultaneously loops to the second instance of i without accepting any input. This is hard to explain but I hope someone can help me out. I have a feeling that getline and cin.ignore are causing these problems. I only came across them on this website.
struct info {
string name;
string address;
int phone;
};
int main(){
int input;
cout<<"How many people do you want on the list" <<endl;
cin>>input;
info arr[input];
for(int i=0;i<input;i++){
cout<<"Enter name for person " <<i+1 <<": ";
getline(cin,arr[i].name);
cin.ignore(1000,'\n');
cout<<"Enter the address of " <<arr[i].name <<endl;
getline(cin,arr[i].address);
cin.ignore(1000, '\n');
cout<<"Enter phone number of " <<arr[i].name <<endl;
cin>>arr[i].phone;
cout<<endl;
}
}
Mixing formatted input with unformatted input is always asking for trouble. When you extract input into the stream, a newline is always appended. The newline character is the delimiter for std::getline(), meaning whilst extraction is being performed, if std::getline() finds a newline, it discards that character and stops input.
This is the problem you're running into. After the last formatted extraction cin >> input, the newline is left in the input stream. std::getline() (unlike formatted input functions) do not discard leading whitespace, so it is prone to the disadvantage I explained above.
To solve this issue, you need to discard leading whitespace manually, so the the non-whitespace input is ready to be extracted by the time std::getline() is invoked:
std::getline(std::cin >> std::ws, arr[i].name);
// ^^^^^^^^^^^^^^^^^^^
std::ws is manipulator which extracts and discards leading whitespace. This should be satisfactory for your purposes.
The following are suggestions that I advise you take. They lead to a substantially cleaner, clearer code structure. And it also reduces a lot of headaches:
1. Your class should have its own inserter/extractor.
You should define your own inserter/extractor to encapsulate I/O semantics for your class. It facilitates a lot of the trouble of manual extraction. Inserters/extractors are typically implemented as friends of the target class so that it may access private data members. Your class in particular has no private data members, but this is still an idiomatic technique nontheless:
struct info
{
string name;
string address;
int phone;
friend std::ostream& operator<<(std::ostream&, const info&);
friend std::istream& operator>>(std::istream&, info&);
};
Inserters/extractors are global functions because they are being defined in terms of (not only) the target class, but also the stream object itself.
2. Use std::vector<T> rather than raw C-style arrays
The following is wrong:
cin >> input;
info arr[input];
This code instantiates an array at compile-time with a size known only at runtime. The possibility of your compiler accepting this code fully depends on how standard-conformant it is. GCC has a non-standard extension which welcomes code like that, but it is non-standard even so and shouldn't be used.
If you need an array of objects whose size is known at runtime, use a vector of those objects. A vector class is already provided for you through the Standard library:
std::cin >> input;
std::vector<info> arr(input);
In fact, the size is superfluous since the vector will grow dynamically as more and more elements are created. You can simply default-construct the vector (unless you have a good reason to specify the size, in which case that many info objects will be default-constructed in the internal buffer of the vector).
std::vector<info> arr;
This is mostly it. Here is an example of your program that applies these two suggestions:
#include <iostream>
#include <vector>
struct info
{
std::string name;
std::string address;
int phone;
friend std::ostream& operator<<(std::ostream&, const info&);
friend std::istream& operator>>(std::istream&, info&);
};
std::ostream& operator<<(std::ostream& os, const info& obj)
{
return os << "Person's name is: " << obj.name << std::endl
<< obj.name << "'s address is: " << obj.address << std::endl
<< obj.name << "'s phone number is: " << obj.phone << std::endl;
}
std::istream& operator>>(std::istream& is, info& obj)
{
std::cout << "Enter name for person " << ": ";
std::getline(is >> std::ws, obj.name);
std::cout << "Enter the address of " << obj.name << std::endl;
std::getline(is >> std::ws, obj.address);
std::cout << "Enter phone number of " << obj.name << std::endl;
std::cin >> obj.phone;
std::cout << endl;
return is;
}
int main()
{
int input;
std::cout << "How many people do you want on the list" << std::endl;
std::cin >> input; // just as a formality, but really the user can enter
// as much as they want.
// The value of input will be ignored.
std::vector<info> arr;
info obj;
while (std::cin >> obj)
{
arr.push_back(obj);
}
for (const auto& item : arr)
std::cout << item << std::endl;
}
If you have any questions, please leave them as a comment and I will try to help you as best as possible.

Reading to end of file with istream_iterator and istream overload

I'm having some trouble reading data from a file into a vector of Orders.
Code:
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <iterator>
using namespace std;
class Purchase;
class Order {
public:
string name;
string address;
vector<Purchase> items;
};
class Purchase {
public:
string product_name;
double unit_price;
int count;
Purchase() {}
Purchase(string pn, double up, int c) :product_name(pn), unit_price(up), count(c) {}
};
istream& operator>>(istream& in, Order& o)
{
string p_name;
double u_price;
int p_count;
getline(in, o.name);
getline(in, o.address);
getline(in, p_name);
in >> u_price >> p_count;
o.items.push_back(Purchase(p_name, u_price, p_count));
return in;
}
ostream& operator<<(ostream& out, const Purchase& p)
{
out << p.product_name << '\n'
<< p.unit_price << '\n'
<< p.count << '\n';
return out;
}
ostream& operator<<(ostream& out, const Order& o)
{
out << '\n' << o.name << '\n'
<< o.address << '\n'
<< o.item << '\n';
return out;
}
int main()
{
cout << "Enter file to read orders from: \n";
string file;
cin >> file;
ifstream is(file.c_str());
istream_iterator<Order> ii(is);
istream_iterator<Order> eos;
ostream_iterator<Order> oo(cout);
vector<Order> orders(ii, eos);
copy(orders.begin(), orders.end(), oo);
}
I have 3 main questions.
1) When I take out the o.item bug in the ostream overload to test output, it only outputs the first entry in the file. The txt file is structured in groups of 5 lines of data that are supposed to be read into vector orders.
Right now the txt file has 10 "orders", but it only reads the first one into the orders vector. I probably need to implement some kind of end of file operation, but I'm not sure how to do this with the istream overload and iterator. This is the biggest problem and if I can figure this out I think I'll probably be okay with the next 2 questions.
2) When that problem is fixed. I will need to deal with the output of o.item (the vector of Purchases in orders which currently can't be output because there is no element being specified). Obviously I need to specify the element to output and I've considered just using a static int and incrementing it, but this would need to be reset for every separate Order, which leads to question 3...
3) If the same name/address are read in as a previous read, I need the program to understand that it is the same "order" being read in and to simply add another object to that Order's Purchases vector rather than creating a new order. I'm thinking about using find() to check if that name already exists in order, and in that case doing nothing with the name/address inputs, but if there is a better way I'd like to know.
Sorry if this is kind of long. If more explanation is needed I'd be happy to elaborate. Any help is appreciated. Thanks.
P.S. Here is an example of input output at the moment if I specify the o.item output to be o.item[0].
Text file has:
John Smith
117 One Tree Hill
Trampoline
600.00
1
//... 9 more Orders like this
Output is:
John Smith
117 One Tree Hill
Trampoline
600.00
1
//... Nothing after this....
Regarding question #3, you could use a multimap instead of a vector.
First, assume you split your Order class up as follows:
class Customer{
public:
string name;
string address;
};
class Purchase {
public:
string product_name;
double unit_price;
int count;
Purchase() {}
Purchase(string pn, double up, int c) :product_name(pn), unit_price(up), count(c) {}
};
class Order {
Customer c;
std::vector<Purchase> p;
};
Now you can simply create a std::multimap<Customer, Purchase>. Adding a customer/purchase pair does exactly what you want: If the customer doesn't already exist, he is added, otherwise the purchase is just added to the existing customer.
Of course, for this to work, you need to define a comparer as well. Simplest way might just be to define operator < for the Customer class. Implement it by comparing the name and disregarding the address.
As for your other questions, avoid mixing getline and stream_iterators. It's not wrong per se, but it gets pretty tricky because getline reads a line at a time, and stream iterators just read to the next whitespace.
Honestly, the C++ IOStreams library is pretty awful to use in general. Since your data format is already cleanly line-separated already, I'd probably just ditch the stream iterators and use getline everywhere.
I haven't looked at your code in detail, but I will give one sentence of advice:
"Do not mix formatted and unformatted input. And in fact, do not use formatted input from files or user input at all."
OK, that was two sentences.
The problem you have is very simple. In fact your code is pretty clear :)
All what you have to add are those simple lines:
istream& operator>>(istream& in, Order& o)
{
string p_name;
double u_price;
int p_count;
getline(in, o.name);
getline(in, o.address);
getline(in, p_name);
in >> u_price >> p_count;
//
while(in.peek() == '\n' || in.peek() == ' ')
{
in.ignore();
}
//
o.items.push_back(Purchase(p_name, u_price, p_count));
return in;
}
The reason is that when using >> it leaves the newline character in the stream unlike getline. You can search Stackoverflow about streams in general there are a lot of great explanations about the issue.
Also, you don't have anything called item in Order. What you have is a vector of Purchase:
ostream& operator<<(ostream& out, const Order& o)
{
out << '\n' << o.name << '\n'
<< o.address << '\n';
//
for(vector<Purchase>::const_iterator i = o.items.begin();
i != o.items.end(); i++)
{
out << *i << '\n';
}
//
return out;
}

c++, using get and >> for ifstream

I have a text file that I am inputting data in from, but I can't seem to get it right.
Here are two lines from the text file as an example (these aren't real people don't worry):
Michael Davidson 153 Summer Avenue Evanston CO 80303
Ingrid Johnson 2075 Woodland Road Aurora IL 60507
Here is the code I have to load the text file and put the data into a struct. I am still new to C++(obviously) and I'm having a hard time using get and >> together. The code I have below, works fine until I get to the "state" and then something goes wrong. Thanks for the help!
//constants
const int FIRST_NAME_LEN = 11;
const int LAST_NAME_LEN = 13;
const int ADDRESS = 25;
const int CITY_NAME_LEN = 16;
const int STATE_LEN = 3;
//define struct data types
struct CustomerType {
char firstName[FIRST_NAME_LEN];
char lastName[LAST_NAME_LEN];
char streetAddress[ADDRESS];
char city[CITY_NAME_LEN];
char state[STATE_LEN];
int zipCode;
};
//prototype function
ifstream& getInfo(CustomerType& CT_Struct, ifstream& infile);
int main() {
//declare struct objects
CustomerType CT_Struct;
ifstream infile("PGM951_customers.txt");
if(!infile) {
cerr << "Could not open the input file." << endl;
exit(1); //terminates the program
}
//call the function
getInfo(CT_Struct, infile);
return 0;
}
ifstream& getInfo(CustomerType& CT_Struct, ifstream& infile) {
while(infile) {
infile.get(CT_Struct.firstName, sizeof(CT_Struct.firstName));
infile.get(CT_Struct.lastName, sizeof(CT_Struct.lastName));
infile.get(CT_Struct.streetAddress, sizeof(CT_Struct.streetAddress));
infile.get(CT_Struct.city, sizeof(CT_Struct.city));
infile.get(CT_Struct.state, sizeof(CT_Struct.state));
infile >> ws;
infile >> CT_Struct.zipCode;
cout << CT_Struct.firstName << " | " << CT_Struct.lastName << " | " << CT_Struct.streetAddress
<< " | " << CT_Struct.city << " | " << CT_Struct.state << " | " << CT_Struct.zipCode << endl;
}
return infile;
}
=== edit ===========
Reading in the state at 8 char was just me messing around and then I forgot to change it back...sorry.
The problem is istream::get() breaks for streetAddress which has spaces in it.
One way is to tokenize the input line first into say, a vector of strings and then depending on the number of tokens convert these to appropriate fields of your CustomerType:
vector<string> tokenize(string& line, char delim=' ') {
vector<string> tokens;
size_t spos = 0, epos = string::npos;
while ((epos = line.find_first_of(delim)) != string::npos) {
tokens.push_back(line.substr(spos, epos - spos));
spos = epos;
}
return tokens;
}
I'd rather a stream extraction operator for CustomerType :
struct CustomerType {
friend istream& operator>>(istream& i, CustomerType& c);
string firstName, lastName, ...;
// ...
};
istream& operator>>(istream& i, CustomerType& c) {
i >> c.firstName >> c.lastName;
string s1, s2, s3;
i >> s1 >> s2 >> s3;
c.streetAddress = s1 + s2 + s3;
i >> c.city >> c.state >> c.zipCode;
return i;
}
You're getting 8 characters for State, which includes all your zipcode, and is larger than your field.
It'd also be tempting to use the skipws operator:
infile >> skipws >> CT_Struct.firstName
>> CT_Struct.lastName
>> ... ;
(Update: that's what I get for doing that from memory. This is more closely approximating correct.)
If I were you I would start again from scratch. I would:
use std::strings instead of character arrays for your data
reads line at a time from the file using std::getline
parse the line up using a stringstream
avoid mixing formatted and unformatted input
My approach to this would be the following:
1) Read each line into a null terminated buffer.
2) Use a split() function that you're gonna have to write. This function should take a string as its input and return a list. It should also take a separator. The separator in this case is ' '.
3) iterate over the list carefully (are there never middle names?) What about 1 word, or 3 word street names? Since many of these columns are really variable in number of words, and you have no seperator other than whitspace, this may prove a fairly tough task. If you NEVER have middle names, you could assume the first two columns are first and last name. You know for sure what the last two are. Everything between them could be assigned to a single address field.

read from file to array of structs within structs in C++

I have asked this question previously here and a similar question was closed.
SO based on a comment from another user, I have reframed my question:
In the first post, I was trying to read tha data from a file into an array with a struct.By using indata << p[i] and is >> p.fId, I was able to read values from data file into PersonId.
Now I want to try this:
struct PersonId
{
int fId;
};
struct PersonData
{
public:
typedef PersonData* Ptr;
PersonData();
PersonId fId;
istream& read(std::istream&);
};
istream& PersonData::read(std::istream& is)
{
is >> fId;
return is;
}
istream& operator >> (istream& is, PersonData &p)
{
// is >> p.fId;
return p.read(is);
}
int main ()
{
ifstream indata; // indata is like cin
int i;
indata.open("persons.txt", ios::in); // opens the file
if(!indata)
{ // file couldn't be opened
cout << "Error: file could not be opened" << endl;
exit(1);
}
int n = 5;
PersonData* p;
p = (PersonData*) malloc (n * sizeof(PersonData));
while ( !indata.eof() )
{
indata >> p[i];
i++;
}
for(i = 0; i < n; ++i)
{
cout << "PersonData [" << i << "] is " << p[i] << endl;
}
return 0;
}
I want to use member function "read" to actually read values into structures defined by PersonData.
My question:
How to read the data from file into PersonId struct which is stored in the PersonData struct??
While reading PersonData[i], I should see it have a struct PersonId with updated value.
I hope my questions are clear now?
You have:
istream& operator >> (istream& is, PersonId &p)
{
is >> p.fId;
return is;
}
missing, after struct PersonId;
You need this to make the is >> fId inside read() work correctly.
Also, fix the cout to use p[i].fId.fId.
Works great!
On a stylistic note, now you're in C++, don't use malloc, use new, and better yet, use a std::vector<>, which will take care of sizing for you.
OK, first some grumbling :-) You say what you want. You wrote how you try. Great. I guess result is not what you expected. But you didn't tell us what is the result you get and why you are disappointed with it. As I look at your code, it shouldn't compile. The problem is here:
istream& PersonData::read(std::istream& is)
{
is >> fId;
return is;
}
I can't see any operator >> defined for PersonId type, and fId is of type PersonId. Am I right? Or maybe there is operator >> defined somewhere and you didn't just paste it into your question? My crystal ball is unclear.
If I guessed properly, the solution is given by Dave Gamble:
istream& operator >> (istream& is, PersonId &p)
{
is >> p.fId;
return is;
}
You wrote "still getting errors in trying to access PersonData". I seems that this time Dave's crystal ball is also unclear, he can't say what problems you have. Neither can I. You have to either provide us details or send us better crystal balls. Maybe you missed his another advice "Also, fix the cout to use p[i].fId.fId." It means, that instead of writing
cout << "PersonData [" << i << "] is " << p[i] << endl;
you should write
cout << "PersonData [" << i << "] is " << p[i].fId.fId << endl;
There can be also another problem - you are not referring to std namespace members consequently - sometimes you write istream, and sometimes you write std::istream, you write endl instead of std::endl. Maybe Koenig's lookup works it out for you, I'm not good at it, but adding std:: prefix may help (of course if this is your problem).