Link to home
Start Free TrialLog in
Avatar of John500
John500Flag for United States of America

asked on

Reading and Writing files

Greetings,

I have a database program designed to allow student entry, searching, deleting etc.  One thing I need is the ability to read a file into the database.

Below is the code I have and which works but when I try to read a file of 100 entries, the whole file doesn't read in, only about 25.

void Database::readDatabase()
{

      char buff[100];
      char fname[40];
      struct find_t ffblk;
      int done;

      cout<<"Choose a file from drive A for reading
               to databse:\n\n";

            //  show directory
            printf("Directory listing of *.*\n");
            done = _dos_findfirst
                       ("*.*",_A_NORMAL,&ffblk);
            while (!done)
            {
            printf("  %s\n", ffblk.name);
                  done = _dos_findnext(&ffblk);
            }
            cin>>fname;
            ifstream input(fname);
            if (!input)
                  {cout<<"File doesn't exist!\n";
                  return;}

            while(input.getline(buff,100)!=0)
            {
            if(strlen(buff)==0)exit(-1);
              Student *tmp = new Student(buff);
            addStudent(tmp);

            }  cout<<"File read to database"; return;

}

The call to new Student(buff) & addStudent(tmp) dynamically creates and adds a student to the database.  There is plenty of memeory on my system so that's not the problem.

Thanks for the help!

Avatar of alexo
alexo
Flag of Antarctica image

Hmmm...

istream::getline() reads a line terminated by a '\n'.
Make sure that no line in the file is longer than 97 characters (100 - carriage-return, line-feed and terminating zero).

Change
  while(input.getline(buff,100)!=0)
to
  while(input.getline(buff, sizeoff buff).good())

See if it helps.
Avatar of John500

ASKER

Alexo,

Glad you could grab this question.

Ok, I replaced that line as you said but still no difference.

Could the problem have anything to do with the file extension being .txt which I'm reading in?

This is a sample of the lines being read into the database:

7868342, Clinton, 00, 0.00, U.S., has a problem with inhaling
2542341, Dick. V., 92, 2.00, U.S., Annoyingly repeats "Baby"
9953891, babson,23, 2.3, alabama, a great guy
9953886, baden, 12, 4, wyoming, nice person

As you can see, each line is not very large.

You may also want to look at the Student::Student constructor that makes each student.  I just sent that to you via e-mail because it will probably more readable.  Does this type of transmission scrable things when I send them?

I don't think the constructor is the problem but to eliminate all doubt...  Also, the addStudent function called in the readDatabase function above, alphabetizes each entry.  I'll send that if you think you need to see it.

Standing by, John
John, I wouldn't have time to check it today.  I suggest you reopen the question for other experts.
Avatar of John500

ASKER

Alex,

Will do as you suggested, thanks for the help thus far.

Anyone out there interested in this question?

John
Do you have empty lines in your file?
while(input.getline(buff,100)!=0)
{
if(strlen(buff)==0)exit(-1);  // this will terminate if you have an empty line
         Student *tmp = new Student(buff);
addStudent(tmp);

}
Hope that helps
 Norbert
Avatar of John500

ASKER

If I have empty lines in my file, and the code below causes termination for empty lines, is there a way to over come these empty lines?
 
while(input.getline(buff,100)!=0)
{
if(strlen(buff)==0)exit(-1);  // this will terminate if you have an empty line
         Student *tmp = new Student(buff);
addStudent(tmp);

}

Avatar of John500

ASKER

How could I make the following line make sense so that getline continues until the end of the file?

while(input.getline(buff,100)!eof(input))
Yes, of course
what about
while(input.getline(buff,100)!=0)
{
if(strlen(buff)!=0)
{
          Student *tmp = new Student(buff);
          addStudent(tmp);
}

}
Avatar of nietod
nietod

How about the following?

while(input.getline(buff,100) != 0)
 {
      if(strlen(buff) != 0)
     {
         Student *tmp = new Student(buff);
         addStudent(tmp);
    }
}


This causes the program to skip empty lines (not use them), but to continue processing when it encounters them.
perhaps you should use the endof file function and change your code to
while(!input.eof())
{
     input.getline(buff,100);
     if(strlen(buff)!=0)
     {
               Student *tmp = new Student(buff);
               addStudent(tmp);
      }
}

Norbert, is there an advantage in using the eof() function?  (I don't use the C++ file I/O.)  I thought that the return vlaue from getline() could be used just as well.
Hi Todd, welcome back.  Did she get it?

< lecture mode on >

An ios has three state bits:
  ios::eofbit - End of file reached.
  ios::failbit - A possibly recoverable formatting or conversion error.
  ios::badbit - A severe I/O error.

ios::eof() checks ios::eofbit.  ios::operator void*() and ios::operator!() check the other two.

< lecture mode off >

Avatar of John500

ASKER

Todd,

The lines you gave will help I'm sure, but it didn't fix the problem.  Below is the code I'm using for the n)ames/notes option. If you agree nothing looks wrong, how about suggesting a way to print the _next and _previous pointers of each student that is being added.  I thinking that the pointer logic might be messed up for each student added.

I've tried cout<<pNew->_next<<pNew->_prev; but the compiler gives me "undefined symbol..."  I don't know why this would happen.  I insert directly after a student is entered.

I've changed my mind, I'll send the code via e-mail.

John


Alex,
     Yes, she got it.  She had already defended, this was just the graduation.  Food, Family and wine.  Especially wine.

     What does getline() return though?  I thought it was the EOF status.  Is it all three state bits?
Nietod,
Alexo told you someting about the bits
I had a look to the online help of vc++ 4.20 and I saw

basic_istream& getline(E *s, streamsize n, E delim = T::newline());
The unformatted input function extracts up to n - 1 elements and stores them in the array beginning at s. It always stores T::eos() after any extracted elements it stores. In order of testing, extraction stops:
·      at end of file
·      after the function extracts an element that compares equal to delim, in which case the element is neither put back nor appended to the controlled sequence
·      after the function extracts is.max_str() elements
 
If the function extracts no elements, it calls setstate(failbit). In any case, it returns *this.

so getline returns always *this and that is probably always !FALSE
So I think there is a big advantage :-)


Avatar of John500

ASKER

Thanks all for the input.

As I said in the last letter, the eof routine will work well if the data is messed up, but I've made sure the file is clean.

The n)ames/notes menu item still only prints about 25 out of 100

John
Avatar of John500

ASKER

I forgot to mention, the routine I'm using to make sure the file is being read is:

...
while(!input.eof())
{
      input.getline(buff,100);
      if(strlen(buff)!=0)
      {
      printf("line %02d = [%s]\n",counter,buff);
      Student *tmp = new Student(buff);
      addStudent(tmp);
      counter++;
      }
    }  cout<<"File read to database"; return;
}

I've also printed _numberStudents after each time the new Student constructor is called which tells me all 100 students have been created.

It has to be either the addStudent func or the printNames func

John
Cograts!

>> What does getline() return though?
The stream reference of course!  Which is implicitly convertable using the two operators I mentioned above.

Avatar of John500

ASKER

Todd,

Anything to report??

Avatar of John500

ASKER

Open to Alex
Avatar of John500

ASKER

Open to Alex, please check your e-mail.

John
Avatar of John500

ASKER

Todd,

Please check your mail

John
>> Open to Alex, please check your e-mail.

Several things:

First, although I appreciate your vote of confidence, I suggest you keep the question open for everybody until you get your solution.

Second, I'm in a different time zone from you (7 hours before the east coast).  You cannot expect to find me online in "normal" (for you) hours.

Third, send me the whole project as a zipped attachment, including the input file and I'll take a look at it.
I would like to see the code too.
because I live in germany I am also in a different time zone

Avatar of John500

ASKER

To whom it may interest:

For a doubly linked list, do insertions to the head or tail automatically set the necessary pointers (3) and how about insertions to the middle and their pointers (4).

The following code is not alphabetizing my student entries.  I believe the entries are unlinked once created:

void Database::addStudent(Student *pNew)
{
      Student *p = _head;
      Student *NxtPtr = _head;
      Student *PrvPtr = NULL;

            while (p)
            {
            if(p->_id == pNew-> _id)
            {cout<<"Duplicate ID, can't be entered!\n";
                return;}

            p = p->_next;
            }

        while (NxtPtr)  // While there are students to test.
      {
      if (strcmp(NxtPtr->_name,pNew->_name) > 0)
        break;         // stop the search.
                                          
      PrvPtr = NxtPtr;       //  Save -> current student.
      NxtPtr = NxtPtr->_next;
      }

      if (PrvPtr != NULL)
      {PrvPtr->_next = pNew;
      _numStudents++; return;}

        else if (PrvPtr == 0)
       {      _head = pNew;
            _numStudents++; return;}

      else if (NxtPtr != NULL)
            NxtPtr->_prev = pNew;
            pNew->_next = NxtPtr;
            pNew->_prev = PrvPtr;
            _numStudents++; return;
}    // end of addStudent
The problem is you don't want the returns in there!  if you move the _numstudents++ to the end (you only want it once) and remove the retuns (or put one at the end) you will be fine.
Actually, that's not 100% true.  You put some bad "elses" in too.  Why did you mess with it?  It should be

while (p)
{
   if(p->_id == pNew-> _id)
   {
      cout<<"Duplicate ID, can't be entered!\n";
      return;
   }
   p = p->_next;
}

while (NxtPtr)  // While there are students to test.
{
   if (strcmp(NxtPtr->_name,pNew->_name) > 0)
      break;    // stop the search.
   PrvPtr = NxtPtr;       //  Save -> current student.
   NxtPtr = NxtPtr->_next;
}
if (PrvPtr != NULL)
  PrvPtr->_next = pNew;
else
   _head = pNew;
if (NxtPtr != NULL)
   NxtPtr->_prev = pNew;
pNew->_next = NxtPtr;
pNew->_prev = PrvPtr;
 _numStudents++;

Note, as you said was a requirement, that sets 3 or 4 pointers every time.
Alex, or anyboady out there.  Do you agree that this is correct?
Avatar of John500

ASKER

How can you say you are meeting the pointer requirements??
..."Note, as you said was a requirement, that sets 3 or 4 pointers every time..."

The only place you are setting pointers is when a student is inserted in the middle.  The head and tail are not automatic!

Avatar of John500

ASKER

...Even when you have set the pointers you left out
PrvPtr->_next = pNew.  It isn't automatic either!!
Avatar of John500

ASKER

Further more,

One occurence of _numStudents at the end is not good.  What happens if no students are entered for some reason?  The number of students will still be incremented.  The increments should take place in the area the student is entered.

As far as returns go, they should be entered where I put them in.  Why stay in the function when the job has been done??
Do you keep a pointer to the tail?  if so that is not being set, although that is a small change (2 lines).  

The head is being set. when it needs to, namely when a new first item is inserted.  When a new first item is inserted, PrvPtr will be NULL,  (Because it was initialized to NULL and was never reset in the loop)  In that case the _head pointer is set after the loop ends.

Don't take my word for it!  Follow in your mind (or on paper) what happens when you (a) add to a totally empty list and (b) add an item that comes before the first item already in the list.
You wrote
>>  Why stay in the function when the  job has been done??
and at the same time you are complaining that the job isn;t getting done (that pointers aren't be set in every case.)

The problem is that the job isn't done at those places you've put in the returns!  The code has to run to the end to get done.  

Trust me.  I've written this sort of thing a couple thousand times in a dozen different languages.  Don't assume I'm wrong and try to change it to what seems right.  Assume I'm right and try to test it in your mind.
Avatar of John500

ASKER

Granted,

You don't have to set the _head->_prev pointer, it is Null by default but you do have to set _head->_next or it will have a default of Null also!  And the _head->_next->_prev must be set.

Same goes with the tail, only the pNew->_prev has to be set and the pNew->_prev->_next pointer.  That is the way it is referenced or by your second pointer PrvPtr->_next = pNew.

Let's put it this way, you were right about for loop being bad, but the instructor's method of pointers was at least getting 25 items in the list, not six.
Avatar of John500

ASKER

Concerning the job being done,

If the while loop is terminated and comes to:

if (PrvPtr != NULL)

and the PrvPtr is Null this condition is passed by, No?  If PrvPtr isn't Null then it isn't passed by and it is executed, No?  If it is executed then a student is entered, No?  If a student is entered then there is no reason to stay in the function.  That goes for any other condition that is entered into and which also ends in a student being entered, the whole function should be done, no other jobs to do.

Are you saying that a condition is executed even if it isn't true?  For (PrvPtr != NULL)?? is this entered if it isn't true??

Your set up wouldn't compile because of a misplaced else statement by-the-way.
You are soooooo confussed.  I have no idea what to say.

(I will say this, the else was placed right in the code above.  I'm not sure why it didn't compile.)

I'm going to take a stab at your problem.  Do you know that in the code

if (NxtPtr != NULL)
    NxtPtr->_prev = pNew;
pNew->_next = NxtPtr;

that the last line ("pNew->_next = NxtPtr") will always be executted?  It is not part of the "if".  only the middle line is part of the "if"?  Do you understand that?  

If that is not you missunderstanding.... give me a call (405) 844-8647.
Avatar of John500

ASKER

Todd,

Congrats!!

The reason the code wouldn't compile is because you didn't put parens around the if or the else statements.

Once I did that, all 100 students were entered in the database!

My problem is with things done behind the scene.  To me, if an "if" statement is false, than NOTHING under it or within its parenthesis, should be executed.

Anyway, good job!  Talk tomorrow



If an "if" is false only the 1st statement after it is not executed.  If that statement is a compound statmenet (a set of braces ({  }) then all the statements withing that compound statement will be skipped.  However execution will resume after the statemetn folowing the "if".

When it comes to executing the stuff in the the parenthesis.  That's a little tricky.  If there is an expression in the parenthesis at least part of the expression will be executed and in some cases all of the expression will be executed.  The compiler will execute as much it needs to in order to determine whehor or not  the the "if" should run.  for example if you have

if (F1() || F2())

The compiler will call F1 first.  If F1 returns true, then the statmenet in the if should run, so it does not call F2.  If F1 returns false, then there still is a possibility that the if should run, depending on F2's return value.  So in that case it calls F2

If you have

if (F1() && F2())

It calls F1 first (always goes left to right)  If F1 returns false, then the "if" should not run.  In that case there is no point in calling F2, so it doesn't. If F1 returns true, the "if" might still run depending on F2's return values, so it does call F2.

As far as the missing parens causing compile problems.  I do not see any missing parens in the sample above.  It is possible that there were ones in the e-mail.  That is my most common typo!  (I'm a pascal programmer mascerading as a C++ programmer.)
Avatar of John500

ASKER

Todd,

Sorry not to write sooner, but my wife went into false labor last night around 3:30.  We got back from the hospital mid morning and I've been sleeping ever since.

The above clarification on what is and is not executed will help.  I still need to study that procedure to see why it is working.  Here is the portion of code I put parenthesis around:

if (PrvPtr != NULL)
  {PrvPtr->_next = pNew;} // added parens
else
   {_head = pNew;} // added parens

if (NxtPtr != NULL)
  NxtPtr->_prev = pNew;
pNew->_next = NxtPtr;
pNew->_prev = PrvPtr;
 _numStudents++;

I cleaned up a couple of other functions in my program last night like the a)ge function and something else (just in case you saw how terrible it was).

I am going to open up a new question now on the delete function, after I study the addStudent one some more.  Who knows it may come to me but I've been having trouble figuring all those different scenarios out also.  The delete is kind of tricky (swaping pointers and such).

Make this question available for grading and I'll do it.  Thanks again for the help!!

ASKER CERTIFIED SOLUTION
Avatar of nietod
nietod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of ozo
Unnecessary, but helpful to ease maintenance if you ever want to change the statement(s)
Well now we're getting into a matter of style....but I think braces are the worst things in the world--other than liver--so I don't use them if I don't have to.  I think they make C and C++ too hard to read.