add NULL terminator for fstream - c++

I'm trying to write a program that will save the contents of a linked list (each individual list is called a locality and includes a mix of data types). The code compiles but terminates unexpectedly; it refers me to the lines in the ifstream library (even though I only want to be using writing)
*_Str = _Elem(); // add terminating null character
Does anyone have an idea whats gone wrong?
//saves a single locality onto a file
void fSave_locality(Clinked *current, fstream *fout)
{
fout->write(current->site_name,100);
fout->write((char*) &current->cood_x,sizeof(double));
fout->write((char*) &current->cood_y,sizeof(double));
fout->write((char *) &current->dip,sizeof(double));
fout->write((char *) &current->strike,sizeof(double));
if (current->next!=NULL) fSave_locality(current->next,fout);
}
void fSave_list(char* fname)
{
fstream *fout;
do
{
cout<<"Would you like to save as a (b)inary or (t)ext file? ";
test = getch();
cout<<"Enter file name (make sure its unique!): ";
cin.getline(fname,100);
if(toupper(test)=='T') fout->open(fname, fstream::out);
if(toupper(test)=='B') fout->open(fname, fstream::out| fstream::binary);
}
while(toupper(test)!='T' || toupper(test)!='B');
if(fout->fail())
{
cout<<"unable to open file.\n";
exit(0);
} //it gets to here without any problems.
current = start;
while(current->next!=NULL)
{
fSave_locality(current, fout);
current=current->next; //repeat for the next object in the list
}
fout->close();
}

I don't understand why you are iterating recursively and sequentially in the same time ?
Just choose one or the other, I changed the code, you also need to set a correct limit to your while loop and make sure you do not use a null object and try to access elements on a null object.
void fSave_locality(Clinked *current, fstream *fout)
{
fout->write(current->site_name,100);
fout->write((char*) &current->cood_x,sizeof(double));
fout->write((char*) &current->cood_y,sizeof(double));
fout->write((char *) &current->dip,sizeof(double));
fout->write((char *) &current->strike,sizeof(double));
//if (current->next!=NULL) fSave_locality(current->next,fout); // comment this out
}
and change the below part :
while(current!=NULL)
{
fSave_locality(current, fout); // you should either comment this one or the recursive one
current=current->next; //repeat for the next object in the list
}

Related

Storing the last value of a file from SD card using arduino

I am making a device that moves back and fourth and needs to store its last position so that upon power up, the last stored value can be grabbed from the last line of the file on an SD card, and it can resume operation. This file will then be destroyed and re-written. For this particular application homing and other methods can not be used because it must start in the spot it last was. Due to position tracking via encoder, there is no positional memory otherwise.The file is setup to be a single data column seperated by commas.
Currently I am successfully writing to the SD card as position changes, and reading the entire file to be printed on the Serial monitor. However, I really only need the last value. The length of the file will always be different do to system operation.
I have read a lot of different solutions but none of them seem to work for my application.
I can read the entire file using:
void read_file() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
Serial.println("test8.txt:");
// read from the file until there's nothing else in it:
// read from the file until there's nothing else in it:
while (myFile.available()) {
String a = "";
for (int i = 0; i < 9; ++i)
{
int j;
char temp = myFile.read();
if (temp != ',' && temp != '\r')
{ //a=temp;
a += temp;
}
else if (temp == ',' || temp == '\r') {
j = a.toInt();
// Serial.println(a);
Serial.println(j);
break;
}
}
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test8.txt");
}
}
This gives me a stream of the values separated by 0 like this:
20050
0
20071
0
20092
0
20113
0
20133
0
Ideally I just need 20133 to be grabbed and stored as an int.
I have also tried:
void read_file_3() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
Serial.println("test8.txt:");
// read from the file until there's nothing else in it:
Serial.println(myFile.seek(myFile.size()));
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
}
This only returns "1", which does not make any sense to me.
Update:
I have found a sketch that does what I want, however it is very slow due to the use of string class. Per post #6 here: https://forum.arduino.cc/index.php?topic=379209.0
This does grab the last stored value, however it takes quite awhile as the file gets bigger, and may blow up memory.
How could this be done without the string class?
void read_file() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
while (myFile.available())
{
String line_str = myFile.readStringUntil(','); // string lavue reading from the stream - from , to , (coma to comma)
int line = line_str.toInt();
if (line != 0) // checking for the last NON-Zero value
{
line2 = line; // this really does the trick
}
// Serial.print(line2);
// delay(100);
}
Serial.print("Last line = ");
Serial.print(line2);
// close the file:
myFile.close();
// SD.remove("test3.txt");
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
}
Any help would be greatly appreciated!
seek returns true if it succesffuly goes to that position and false if it does not find anything there, like for instance if the file isn't that big. It does not give you the value at that position. That's why you see a 1, seek is returning true that it was able to go to the position (myFile.size()) and that's what you're printing.
Beyond that, you don't want to go to the end of the file, that would be after your number. You want to go to a position 5 characters before the end of the file if your number is 5 digits long.
Either way, once you seek that position, then you still need to use read just like you did in your first code to actually read the number. seek doesn't do that, it just takes you to that position in the file.
EDIT: Since you edited the post, I'll edit the answer to go along. You're going backwards. You had it right the first time. Use the same read method you started with, just seek the end of the file before you start reading so you don't have to read all the way through. You almost had it. The only thing you did wrong the first time was printing what you got back from seek instead of seeking the right position and then reading the file.
That thing you looked up with the String class is going backward from where you were. Forget you ever saw that. It's doing the same thing you were already doing in the first place only it's also wasting a lot of memory and code space in the process.
Use your original code and just add a seek to skip to the end of the file.
This assumes that it's always a 5 digit number. If not then you may need a little bit of tweaking:
void read_file() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
Serial.println("test8.txt:");
/// ADDED THIS ONE LINE TO SKIP MOST OF THE FILE************
myFile.seek(myFile.size() - 5);
// read from the file until there's nothing else in it:
// read from the file until there's nothing else in it:
while (myFile.available()) {
String a = "";
for (int i = 0; i < 9; ++i)
{
int j;
char temp = myFile.read();
if (temp != ',' && temp != '\r')
{ //a=temp;
a += temp;
}
else if (temp == ',' || temp == '\r') {
j = a.toInt();
// Serial.println(a);
Serial.println(j);
break;
}
}
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test8.txt");
}
}
See, all I've done is take your original function and add a line to seek the end to it.

ifstream not working with dirent.h

I'm testing optimizations for dijkstra algorithm and to make it easier to open files I used "dirent.h" to get all the test files in the running path and then ifstream to open this file.
the readDirec method reads all the files in the directory and ignores folder and puts those files names in a vector called files.
void selectDirec(){
files.clear();
DIR *dir;
struct dirent *ent;
if ((dir = opendir (".")) != NULL) {
while ((ent = readdir (dir)) != NULL) {
if(opendir(ent->d_name) == NULL){
files.push_back(ent->d_name);
}
}
closedir (dir);
} else {
cout<<"directory error"<<endl;
}
}
after that I uses a function called selectFile which assigns the name of the file the user chooses to a variable called fileName.
void selectFile(){
selectDirec();
for(int i = 0 ; i < files.size() ; i++){
cout<<i+1<<" : "<<files[i]<<endl;
}
int choice = 0;
do{
cout<<"enter file number"<<endl;
cin>>choice;
}while(choice > files.size());
choice--;
fileName = files[choice];
cout<<fileName<<":"<<endl;
}
after that I enter my readGraph function which opens the file and continue graph operations
void readGraph(){
ifstream ifile; ifile.open(fileName);
if(!ifile.is_open()){
cout<<"no file with the name specified"<<endl;
eflag = true;
return;
}
...
...
}
initialization:
vector<char *> files;
char * fileName ;
now I have those 5 files to test which I got from here http://algs4.cs.princeton.edu/44sp/:
tinyEWD.txt contains 8 vertices and 15 edges [140B]
mediumEWD.txt contains 250 vertices and 2,546 edges[40KB]
1000EWG.txt contains 1,000 vertices and 16,866 edges[313KB]
10000EWG.txt contains 10,000 vertices and 123,462 edges[2.4MB]
NYC.txt . contains 264346 vertices and 733846 edges[12.7MB].
but there's a weird problem with those 3 files:
'mediumEWD' , '10000EWD.txt' , 'NYC.txt'
when I choose any of them the code shows me "no file with the name specified" that in the else statement in readGraph.
but when I enter their name manually and comment selectDirec and selectFile the program opens them successfully.
P.S. I checked the file name and spacing and everything.
P.S.2 currently running this code on ubuntu 14.04 LTS.
thanks in advance.
if(opendir(ent->d_name) == NULL){
files.push_back(ent->d_name);
}
What is files? I suspect that you are using a std::vector<const char *>, or something along the same lines.
This won't work. d_name is a part of the dirent structure. Immediately afterwards, and certainly after the closedir(), that pointer is no longer valid, and points to deallocated memory.
Looks to me like you then proceed and attempt to use the no-longer valid pointer as the filename parameter to std::ifstream.
You should use a std::vector<std::string> to store the filenames, and use the c_str() member function to extract a pointer to a C-style string, for the open() call.
You can't be using a vector of std::strings here, this must be a vector of raw character pointers. That's because you're assigning one of its values to fileName, whatever it is, and then passing it directly to open() without using c_str(). So it can't be a vector of strings.

ifstream always returns "ELF" to object

I wrote the following method to check whether my program works correctly with file IO, but it most definitely doesn't work. All that I get from inFile is "ELF", can anyone tell me why? My objects work perfectly fine with other types of istreams.
void testFiles(int ct, char ** args)
{
if(ct<2){
cout<<"Invalid number of arguments. Must be two files, one for input, one for output."<<endl;
return;
}
ifstream inFile;
inFile.open(args[0]);
Tree<Word,int> x;
Word *key;
Word *val;
cout<<"Tree extracted from file: "<<endl;
while(inFile.good()&&inFile.is_open()){
key = new Word();
val = new Word();
inFile>>*key;
inFile>>*val;
if(!inFile.good()){
cout<<"Error: incomplete key-value pair:"<<key->getStr()<<endl;
break;
}
cout<<key->getStr()<<" "<<val->getStr()<<endl;
x[*key] = val->asInt();
delete key;
delete val;
}
inFile.close();
ofstream outFile;
outFile.open(args[1]);
cout<<"Tree as read from file:"<<endl<<x;
outFile<<x;
outFile.close();
}
args[0] is not the first argument to your program. It's the name of the executable file itself.
What's happening is that you're opening your own executable file, rather than the file specified on the command line, and since your program is a linux binary, you're reading in the magic string at the start of ELF binaries, which is "ELF".
To fix the error, change args[0] to args[1].
You are specifying the program name instead of the first param as a file name.
It is a good idea to check for the validity of what U do. ie. check either whether the args[1] is not empty and/or the return value of file open... Only checking the parameter count is not enough.

Basic C++ program, getline()/parsing a file

I've been tasked with creating a small program that is to parse through a text file and grab necessary info from it. The file is laid out as such
Tuesday*Info5051*10:00*11:00*M3039*Info5064*12:00*3:00*G1001;
Basically it's supposed to store each string in a struct so that I can later retrieve it, but I'm unable to get my program to work (I have a learning disability so things tend to get difficult). Here's my code so far. (I know it's a simple program but I tend to overthink/screw up stuff.) My big problem I've hit so far is that it won't open the file to start. I've saved the file to the bin->debug as well as the main folder of the program. I'm sure I'm using the getline method wrong.
struct Course
{
string _sDay;
string _sName;
string _sCode;
string _iStart;
string _iDuration;
string _sRoom;
};
int main()
{
ifstream fileIn;
fileIn.open("courseLoad.txt");
vector<Course> vCourse;
string str="*";
string line;
if (!fileIn)
{
cout<<"A error has occured, please contact support.";
}
while(!fileIn.eof())
{
for(int i=0; i!= fileIn.eof();i++)
{
//file.getline(entry.part_num, 6, '-');
getline(fileIn,line,'*');
vCourse[i]._sDay =line;
getline(fileIn,line,'*');
vCourse[i]._sName =line;
getline(fileIn,line,'*');
vCourse[i]._sCode = line;
getline(fileIn,line,'*');
vCourse[i]._iStart =line;
getline(fileIn,line,'*');
vCourse[i]._iDuration = line;
getline(fileIn,line,'*');
vCourse[i]._sRoom =line;
cout<<vCourse[i];
}//end for
}
--output to screen here--
There are several issue with this code:
1) That code is missing a return statement or an else statement to prevent the program from continuing its execution in case it cannot open the file:
if (!fileIn)
{
cout<<"A error has occured, please contact support.";
return -1;
}
2) Your getline all operate on the same input stream. You want to read in a line, then parse that line. For example:
// Read in a line
while (getline(fileIn,line))
{
string item;
std::stringstream sstr(line);
// Read in an item
while (getline(sstr, item, "*"))
{
std::cout << item << std::endl;
}
}
3) vCourse size is 0, so you cannot use the [] operator; but you can use push_back to expand the size of the vector and insert an element at the back of the vector:
// Read in a line
while (getline(fileIn,line))
{
string item;
// Default course construction
Course c;
std::stringstream sstr(line);
// Read in an item
getline(sstr,item,'*');
c._sDay = item;
getline(sstr,item,'*');
c._sName = item;
getline(sstr,item,'*');
c._sCode = item;
getline(sstr,item,'*');
c._iStart = item;
getline(sstr,item,'*');
c._iDuration = item;
getline(sstr,item,'*');
c._sRoom = item;
// Save the course into the vector
vCourse.push_back(c);
}
You could also add some more error checking in the above (in case some elements are missing from the line).
One immediate problem that is clear is that you are not actually adding any Course structs into your vector but you are assigning to the elements of them as if you are. For example
vCourse[i]._sDay =line;
but you have not actually added an instanct of a Course struct to the vector at index i. This means you assign to an instance that is not present and that is never good news. What you need prior to this is
Course newItem; // make a new Course object instance
vCourse.push_back(newItem); // This adds the instance to the end of the vector
// Now assign to the members of vCourse[i];
vCourse[i]._sDay =line;
getline(fileIn,line,'*');
vCourse[i]._sName =line;
getline(fileIn,line,'*');
vCourse[i]._sCode = line;
getline(fileIn,line,'*');
vCourse[i]._iStart =line;
getline(fileIn,line,'*');
vCourse[i]._iDuration = line;
getline(fileIn,line,'*');
then you can assign to the struct.
Also if you want to do this
cout<<vCourse[i];
you will need to overload the operator<<
If you are unable to open your file, you need to check that you have 1) spelled the filename correctly and 2) that the file is in the same location as your executable. Probably would be safer to write the full pathname anyway
You can also try to put the content of file into single string and use strtok() function.

Reading multiple files

I want to alternate between reading multiple files. Below is a simplified version of my code.
ifstream* In_file1 = new ifstream("a.dat", ios::binary);
ifstream* In_file2 = new ifstream("b..dat", ios::binary);
ifstream* In_file;
int ID;
In_file = In_file1;
int ID = 0;
//LOOPING PORTION
if (In_file -> eof())
{
In_file -> seekg(0, ios_base::beg);
In_file->close();
switch (ID)
{
case 0:
In_file = In_file2; ID = 1; break;
case 1:
In_file = In_file1; ID = 0; break;
}
}
//some codes
:
:
In_file->read (data, sizeof(double));
//LOOPING PORTION
The code works well if I am reading the files one time and I thought that everything was cool. However, if the part termed 'looping portion' is within a loop, then the behaviour becomes weird and I start having a single repeating output. Please, can someone tell me what is wrong and how I can fix it? If you have a better method of tacking the problem, please suggest. I appreciate it.
//SOLVED
Thank you everybody for your comments, I appreciate it. Here is what I simple did:
Instead of the original
switch (ID)
{
case 0:
In_file = In_file2; ID = 1; break;
case 1:
In_file = In_file1; ID = 0; break;
}
I simply did
switch (ID)
{
case 0:
In_file = new ifstream("a.dat", ios::binary); ID = 1; break;
case 1:
In_file = new ifstream("b.dat", ios::binary); ID = 0; break;
}
Now it works like charm and I can loop as much as I want:-). I appreciate your comments, great to know big brother still helps.
Let's see: the code you posted works fine, and you want us to tell you
what's wrong with the code you didn't post. That's rather difficult.
Still, the code you posted probably doesn't work correctly either.
std::istream::eof can only be used reliably after an input (or some
other operation) has failed; in the code you've posted, it will almost
certainly be false, regardless.
In addition: there's no need to dynamically allocate ifstream; in
fact, there are almost no cases where dynamic allocation of ifstream
is appropriate. And you don't check that the opens have succeeded.
If you want to read two files, one after the other, the simplest way is
to use two loops, one after the other (calling a common function for
processing the data). If for some reason that's not appropriate, I'd
use a custom streambuf, which takes a list of filenames in the
constructor, and advances to the next whenever it reaches end of file on
one, only returning EOF when it has reached the end of all of the
files. (The only complication in doing this is what to do if one of the
opens fails. I do this often enough that it's part of my tool kit,
and I use a callback to handle failure. For a one time use, however,
you can just hard code in whatever is appropriate.)
As a quick example:
// We define our own streambuf, deriving from std::streambuf
// (All istream and ostream delegate to a streambuf for the
// actual data transfer; we'll use an instance of this to
// initialize the istream we're going to read from.)
class MultiFileInputStreambuf : public std::streambuf
{
// The list of files we will process
std::vector<std::string> m_filenames;
// And our current position in the list (actually
// one past the current position, since we increment
// it when we open the file).
std::vector<std::string>::const_iterator m_current;
// Rather than create a new filebuf for each file, we'll
// reuse this one, closing any previously open file, and
// opening a new file, as needed.
std::filebuf m_streambuf;
protected:
// This is part of the protocol for streambuf. The base
// class will call this function anytime it needs to
// get a character, and there aren't any in the buffer.
// This function can set up a buffer, if it wants, but
// in this case, the buffering is handled by the filebuf,
// so it's likely not worth the bother. (But this depends
// on the cost of virtual functions---without a buffer,
// each character read will require a virtual function call
// to get here.
//
// The protocol is to return the next character, or EOF if
// there isn't one.
virtual int underflow()
{
// Get one character from the current streambuf.
int result = m_streambuf.sgetc();
// As long as 1) the current streambuf is at end of file,
// and 2) there are more files to read, open the next file
// and try to get a character from it.
while ( result == EOF && m_current != m_filenames.eof() ) {
m_streambuf.close();
m_streambuf.open( m_current->c_str(), std::ios::in );
if ( !m_streambuf.is_open() )
// Error handling here...
++ m_current;
result = m_streambuf.sgetc();
}
// We've either gotten a character from the (now) current
// streambuf, or there are no more files, and we'll return
// the EOF from our last attempt at reading.
return result;
}
public:
// Use a template and two iterators to initialize the list
// of files from any STL sequence whose elements can be
// implicitly converted to std::string.
template<typename ForwardIterator>
MultiFileInputStreambuf(ForwardIterator begin, ForwardIterator end)
: m_filenames(begin, end)
, m_current(m_filenames.begin())
{
}
};
#include <iostream>
#include <fstream>
#include <string>
#define NO_OF_FILES 2
int main ()
{
std::ifstream in;
std::string line;
std::string files[NO_OF_FILES] =
{
"file1.txt",
"file2.txt",
};
// start our engine!
for (int i = 0; i < NO_OF_FILES; i++)
{
in.open(files[i].c_str(), std::fstream::in);
if (in.is_open())
{
std::cout << "reading... " << files[i] << endl;
while (in.good())
{
getline(in, line);
std::cout << line << std::endl;
}
in.close();
std::cout << "SUCCESS" << std::endl;
}
else
std::cout << "Error: unable to open " + files[i] << std::endl;
}
return 0;
}