C++ reading from file function failing to exit while cycle [duplicate] - c++

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
End of File in C++
I wrote a function to read data from a .txt file, but when I call it, it keeps crashing.
Here's the code:
void beolvas(vector<int> &charge, vector<int> &deliver, vector<Robot*> &robots) {
string s;
ifstream f;
do {
cout << "Add meg a filenevet" << endl;
cin >> s;
f.open(s.c_str());
} while (!f.good());
cout << "adatok beolvasasa..." << endl;
int napok;
if (!(f >> napok)) throw 1;
charge.resize(napok);
deliver.resize(napok);
for (int i = 0; i<napok; i++) {
if (!(f>>charge[i])) throw 1;
if (!(f>>deliver[i])) throw 1;
}
string type, name;
int battery;
while (!f.eof()) {
cout << " a ";
if (f>>type && f>>name && f>>battery) {
if (type=="Mac") {
Mac r = Mac(name,battery);
robots.push_back(&r);
};
if (type=="Eco") {
Eco r = Eco(name,battery);
robots.push_back(&r);
};
if (type=="Pro") {
Pro r = Pro(name,battery);
robots.push_back(&r);
};
};
};
}
It seems the problem occurs in the while loop. If I want to read from a 3 row long text, I get 4 letter a-s on the screen (I have the program print one before reading every row).
Is f.eof() not the function I need to use here?

This is one of the most common problems with reading from files. Checking f.eof() will only tell if you the previous read hit the end of the file. Hence, after the last successful read (where you got your third "a" printed), eof() returns false and your loop executes one more time, outputting an extra "a".
Instead, use your extraction line as the condition for your while loop:
while (f>>type && f>>name && f>>battery) {
if (type=="Mac") {
Mac r = Mac(name,battery);
robots.push_back(&r);
}
if (type=="Eco") {
Eco r = Eco(name,battery);
robots.push_back(&r);
}
if (type=="Pro") {
Pro r = Pro(name,battery);
robots.push_back(&r);
}
}
This works because as soon as one of your extractions hits the end of the file, the while condition will be false and the loop won't execute.
If you want to continue after badly formatted lines, I recommend that you use std::getline instead:
std::string line;
while (std::getline(f, line)) {
std::stringstream ss(line);
if (ss >> type && ss >> name && ss >> battery) {
// ...
}
}
The reason for using this std::getline approach is because you would have had a problem if half of a line had been correctly formatted. Your approach would have read in perhaps type and name then failed on battery and the next iteration of the loop would have started from the same place.
You're also going to have a problem with the fact you're pushing pointers to objects that have automatic storage duration into some vectors. When the block in which the objects are created ends, the pointers will be pointing to an invalid object.

This is a FAQ. In C/C++, the end-of-file condition is triggered only after you have tried to read past the end. So, you can't assume from feof() == false that input is available (because you could be exactly at the end of the file, but not yet past it). You should always test for valid input after a read operation.

Related

Segmentation fault when reading text file

I am trying to run a code sorting the ages of Titanic survivors from a text file. It compiles fine, but the program terminates simply saying "segmentation fault" when I choose option B (option A is not written yet.)
Here is a small sample of the text file for reference.
29 1stClass TRUE
0.9 1stClass TRUE
2 1stClass FALSE
30 1stClass FALSE
I've isolated the error to the chunk where the file is processed (//actual processing), but I'm not sure what exactly is wrong.
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <fstream>
#include <ctype.h>
void sortSurvivors();
void sortAgesLiving();
int main()
{
char options;
std::cout << "Titanic Data \nOptions \nA) Display count of people who lived and died... \nB) Display count of people who lived by age... \nPlease select option (A-B)...";
std::cin >> options;
switch (options)
{
case 'A':
sortSurvivors();
break;
case 'B':
sortAgesLiving();
break;
}
}
void sortSurvivors()
{
}
void sortAgesLiving()
{
std::ifstream inputFile;
std::string filename = "TitanicData.txt";
std::string age;
std::string classBoat;
std::string survival;
bool survived;
int eldest = 0;
//pre-sort processing
while (inputFile >> age >> classBoat >> survival)
{
int ageConv = stoi(age);
//G is for the ghetto fix I am pulling here, because I recieve an error when using "TRUE" as a string
char gchar = 'G';
survival += gchar;
if (survival == "TRUEG")
{
survived = true;
}
else
{
survived = false;
}
if (eldest < ageConv)
{
eldest = ageConv;
}
}
//initialize vector
std::vector<int> survivorVector;
for (int i = 0; i < eldest; i++)
{
survivorVector.push_back(0);
}
inputFile.open(filename);
//actual processing (ERROR HERE)
if (inputFile)
{
while (inputFile >> age >> classBoat >> survival)
{
int ageConv = stoi(age);
if (survived = true)
{
survivorVector[ageConv] = survivorVector[ageConv] + 1;
}
for (int j = 0; j <= eldest; j++)
{
std::cout << j << "\t" << survivorVector[j] << "\n";
}
}
// Close the file.
inputFile.close();
}
else
{
std::cout << "I don't know what broke, but uhhhhhhhhhh oops.";
}
}
As per usual I'm sure it's something dumb I overlooked.
In sortAgesLiving(), you have forgotten to open the file before starting your pre-sort processing. As a consequence, your first reading loop will fail to read anything at all. Therefore eldest will stay 0.
You then construct a vector and populate it. But since the loop is based on eldest, the vector survivorVector will stay empty.
When you finally open the file and read it, the first line will be considered as a survivor since you accidentally overwrite the boolean with true (i.e. if (survived = true) instead of if (survived == true) or simply if (survived). You'll then try to access the vector out of bounds.
Even if you correct this error, at the first survivor you'll get again out of bounds. Accessing a vector out of bounds is UB and one of the many possible symptoms can be segmentation fault.
Miscellaneous advices (not related to your issues):
You have an ambiguous age of 0.9. Converting it to an int will cause it to be 0. Is this ok, or do you need to round this up?
If it's rounding up, you could make the age variable a double and read it directly without conversion. You could then convert it mathematically to an integer age rounding it up or truncating it, as needed. If you're sure to have only integers, you could make the variable an int and not worry at all.
It is unsafe to trust a value in a file to directly index a vector. What if between the two reading phases, an additional line would have been added by someone else to the file with a value higher than eldest ? What if the value read would be negative? Better always check that it's in an acceptable range before using a value as an index. It can save you hours of debugging and your customers some nightmares.
Finally, the two-phase read is not necessary: you could just read the age, and after having checked that it's positive and smaller than 150 years (quite optimistic), you could, if needed, resize your vector if the age is equal or larger than the current vector size. Why? Imagine you work one day for US census with files having millions of lines: the fewer passes over the file, the better ;-)

How to determine in C++ if an element in a text file is a character or numeric?

I am trying to write a code in C++ reading a text file contains a series of numerics. For example, I have this .txt file which contains the following series of numbers mixed with a character:
1 2 3 a 5
I am trying to make the code capable of recognizing numerics and characters, such as the 4th entry above (which is a character), and then report error.
What I am doing is like
double value;
while(in) {
in >> value;
if(!isdigit(value)) {
cout << "Has non-numeric entry!" << endl;
break;
}
else
// some codes for storing the entry
}
However, the isdigit function doesn't work for text file. It seems when I am doing in >> value, the code will implicitly type-cast a into double.
Can anyone give me some suggestion?
Thanks a lot!
Your while loop doesn't do what you think it does.
It only iterates one statement:
in >> value;
The rest of the statements are actually outside the loop.
Using curly braces for the while body is always recommended
I created a small mini script where I would be reading in a file through a standard fstream library object as I was a little unsure on what your "in" represented.
Essentially, try to read in every element as a character and check the digit function. If you're reading in elements that are not of just length 1, a few modifications would have to be made. Let me know if that's the case and I'll try to help!
int main() {
std::fstream fin("detect_char.txt");
char x;
while (fin >> x) {
if (!isdigit(x)) {
std::cout << "found non-int value = " << x << '\n';
}
}
std::cout << '\n';
return 0;
}
Try reading the tokens into string and explicitly parsing it
ifstream infile("data.txt");
string token;
while (infile >> token) {
try {
double num = stod(token);
cout << num << endl;
}
catch (invalid_argument e) {
cerr << "Has non-numeric entry!" << endl;
}
}
Since it looks like the Asker's end goal is to have a double value for their own nefarious purposes and not simply detect the presence of garbage among the numbers, what the heck. Let's read a double.
double value;
while (in) // loop until failed even after the error handling case
{
if (in >> value) // read a double.
{
std::cout << value; // printing for now. Store as you see fit
}
else // failed to read a double
{
in.clear(); // clear error
std::string junk;
in >> junk; // easiest way I know of to read up to any whitepsace.
// It's kinda gross if the discard is long and the string resizes
}
}
Caveat:
What this can't handle is stuff like 3.14A. This will be read as 3.14 and stop, returning the 3.14 and leave the A for the next read where it will fail to parse and then be consumed and discarded by in >> junk; Catching that efficiently is a bit trickier and covered by William Lee's answer. If the exception handling of stod is deemed to expensive, use strtod and test that the end parameter reached the end of the string and no range errors were generated. See the example in the linked strtod documentation

Files with commas in c++

How to read in a text file with commas and write it out to a new file without commas?
The file has nine columns- 1st is month next is date and there is a comma, then year then there are 6 columns of numbers representing dollars with a comma in between them for example 17,751.24
I need to make a new text file out of it that has no commas.
I was trying to use double variables for each coloumn but the commas forced me to use string variables then i tried to read it in with a while loop:
While(infile >> month >> date >> year >> open >> high >> low >> close >> volume >> adj)
{
...
}
Side note : the file is about some stock exchange thing.
How do I make a new text file with the same content except there should be no commas.
This is for a beginners c++ class so please avoid using advanced stuff in answers.
Thanks in advance.
Perhaps this is still too soon, but as what I provide below is not a complete answer, I hope it might help to get you going.
The simple question seems to be:
How to read in a text file with commas and write it out to a new file
without commas?
This simple question is broad (with many approaches), but you might consider the following:
// To use:
// invoke with i/o redirection
//
// Example with executable named t160
// and some file named 'inputFile':
//
// t160 < inputFile > noCommaFile
//
int t160()
{
int commaCount = 0;
do {
int kar = 0;
kar = std::cin.get(); // read single char from cin
if(std::cin.eof()) { break; } // break out when we finish
if(std::cin.bad()) { // break out on file i/o error
std::cerr << "ERR: std::cin.bad() " << std::endl;
break;
}
if(',' == kar) { commaCount += 1; continue; } // found a comma
// ^^^^^^^^ continue loop: do not cout the comma
std::cout << char(kar); // not a comma, so cout the kar
}while(1);
// uncomment for test:
// std::cerr << "commaCount = " << commaCount << std::endl;
return(0);
}
This code reads one char at a time, and writes one char at a time or nothing.
When a comma is found, it is 'discarded'. All other kars are included in the output.
You might later decide that you want to replace the comma with a space (instead of discarding it).
I can imagine that you might decide to treat a comma-space (", ") differently than a comma-digit (as in "123,456").
This simple loop should support those decisions, and many more.
Good luck.

How to convert vector to string and convert back to vector

----------------- EDIT -----------------------
Based on juanchopanza's comment : I edit the title
Based on jrok's comment : I'm using ofstream to write, and ifstream to read.
I'm writing 2 programs, first program do the following tasks :
Has a vector of integers
convert it into array of string
write it in a file
The code of the first program :
vector<int> v = {10, 200, 3000, 40000};
int i;
stringstream sw;
string stringword;
cout << "Original vector = ";
for (i=0;i<v.size();i++)
{
cout << v.at(i) << " " ;
}
cout << endl;
for (i=0;i<v.size();i++)
{
sw << v[i];
}
stringword = sw.str();
cout << "Vector in array of string : "<< stringword << endl;
ofstream myfile;
myfile.open ("writtentext");
myfile << stringword;
myfile.close();
The output of the first program :
Original vector : 10 200 3000 40000
Vector in string : 10200300040000
Writing to File .....
second program will do the following tasks :
read the file
convert the array of string back into original vector
----------------- EDIT -----------------------
Now the writing and reading is fine, thanks to Shark and Jrok,I am using a comma as a separator. The output of first program :
Vector in string : 10,200,3000,40000,
Then I wrote the rest of 2nd program :
string stringword;
ifstream myfile;
myfile.open ("writtentext");
getline (myfile,stringword);
cout << "Read From File = " << stringword << endl;
cout << "Convert back to vector = " ;
for (int i=0;i<stringword.length();i++)
{
if (stringword.find(','))
{
int value;
istringstream (stringword) >> value;
v.push_back(value);
stringword.erase(0, stringword.find(','));
}
}
for (int j=0;j<v.size();i++)
{
cout << v.at(i) << " " ;
}
But it can only convert and push back the first element, the rest is erased. Here is the output :
Read From File = 10,200,3000,40000,
Convert back to vector = 10
What did I do wrong? Thanks
The easiest thing would be to insert a space character as a separator when you're writing, as that's the default separator for operator>>
sw << v[i] << ' ';
Now you can read back into an int variable directly, formatted stream input will do the conversion for you automatically. Use vector's push_back method to add values to it as you go.
Yes, this question is over a year old, and probably completely irrelevant to the original asker, but Google led me here so it might lead others here too.
When posting, please post a complete minimal working example, having to add #include and main and stuff is time better spent helping. It's also important because of your very problem.
Why your second code isn't working is all in this block
for (int i=0;i<stringword.length();i++)
{
if (stringword.find(','))
{
int value;
istringstream (stringword) >> value;
v.push_back(value);
stringword.erase(0, stringword.find(','));
}
}
istringstream (stringword) >> value interprets the data up to the comma as an integer, the first value, which is then stored.
stringword.find(',') gets you the 0-indexed position of the comma. A return value of 0 means that the character is the first character in the string, it does not tell you whether there is a comma in the string. In that case, the return value would be string::npos.
stringword.erase deletes that many characters from the start of the string. In this case, it deletes 10, making stringword ,200,3000,40000. This means that in the next iteration stringword.find(',') returns 0.
if (stringword.find(',')) does not behave as wished. if(0) casts the integer to a bool, where 0 is false and everything else is true. Therefore, it never enters the if-block again, as the next iterations will keep checking against this unchanged string.
And besides all that there's this:
for (int j=0;j<v.size();i++)
{
cout << v.at(i) << " " ;
}
it uses i. That was declared in a for loop, in a different scope.
The code you gave simply doesn't compile, even with the added main and includes. Heck, v isn't even defined in the second program.
It is however not enough, as the for condition stringword.length() is recalculated every loop. In this specific instance it works, because your integers get an extra digit each time, but let's say your input file is 1,2,3,4,:
The loop executes normally three times
The fourth time, stringword is 4, stringword.length() returns 2, but i is already valued 3, so i<stringword.length() is invalid, and the loop exits.
If you want to use the string's length as a condition, but edit the string during processing, store the value before editing. Even if you don't edit the string, this means less calls to length().
If you save length beforehand, in this new scenario that would be 8. However, after 4 loops string is already empty, and it executes the for loop some more times with no effect.
Instead, as we are editing the string to become empty, check for that.
All this together makes for radically different code altogether to make this work:
while (!stringword.empty())
{
int value;
istringstream (stringword) >> value;
v.push_back(value);
stringword.erase(0, stringword.find(',')+1);
}
for (int i = 0; i < v.size(); i++)
{
cout << v.at(i) << " " ;
}
A different way to solve this would have been to not try to find from the start, but from index i onwards, leaving a string of commas. But why stick to messy stuff if you can just do this.
And that's about it.

c++ read in array struct trouble

i am experiencing much trouble reading in files from input into an array struct. here is the code if someone can tell me what im doing wrong i can figure it out. the loop is supposed to be reading 2 strings, and 1 int, and skipping possible blank lines. but when i run it, it reads the first set and doesnt read nothing after that.
struct Instruments
{
string model;
string maker;
int year;
};
int main()
{
int size;
Instruments data[20];
int i =0;
ifstream fin;
fin.open("input.txt");
for (i=0; i<20; i++)
{
do{
getline(fin, data[size].model);
getline (fin, data[size].maker);
fin >> data[size].year;
size++;
}
while (data[size].model.length() > 0);
}
fin.close();
for(int i=0;i<size; i++)
{
cout << data[i].model << "model"<<endl;
cout << data[i].maker << "maker" << endl;
cout << data[i].year<< " year" << endl;
}
return 0;
}
There are multiple issues here:
Your first 'for' loop is using i as the loop counter but size as the array index.
After this call:
fin >> data[size].year
it will read to the end of the number and any whitespace that follows will form part of your next read, so if you are expecting to start the next record at the next line, do a blank getline() here too.
Aside from that.
Use vectors not arrays
Have a method to read from a stream into your struct, and if that succeeds, use push_back() to add it to your vector.
That doesn't necessarily mean you have to loop until the read fails, it may be that you know in advance how many you wish to read. But you should still do it this way.
size variable is not initialized. In C++, variables are not automatically initialized.
You must add:
int size = 0;
This is just a guess. In addition to the missing initialization of size, the following:
do{
.....
}
while (data[size].model.length() > 0);
looks also quite suspect to me: as soon as data[size].model has some content (which it does after the first read, this will evaluate to true and you probably have an infinite loop.
If you craft the for loop correctly, you don't need the do-while loop.