You are here:

C++/Linking "ifstream ins" to file "booklist2.txt"

Advertisement


Question
Dear C++ expert,
I am having trouble linking the compiler to an outer file in order to load the data from the outer file into an array of a struct I defined.

Here goes the code:

#include <iostream>
#include <string>
#include <fstream>
using namespace std;

struct Books
{
  string isbn, title, fn, ln;
};

const int size = 5;

void loadData (ifstream&, Books[]);
void showData (const Books[]);

void main ()
{
  Books bookList [size];
  ifstream ins;
  ins.open("booklist2.txt");
  if (ins.fail())
     cerr << "ERROR\n";
  loadData (ins, bookList);
  showData (bookList);
  ins.close();
}

void loadData (ifstream& in, Books b[])
{
  cout << "Loading";
  for (int i = 0; i < size; i++)
  {
     in >> b[i].isbn;
     in.ignore(1);
     getline (in, b[i].title, '\t');
     in >> b[i].fn >> b[i].ln;
     cout << ".";
  }
  cout << "Done\n";
}

void showData (const Books b[])
{
  for (int i = 0; i < size; i++)
     cout << b[i].isbn << "\t" << b[i].title
         << "\t" << b[i].fn << "\t"
         << b[i].ln << endl;
}

The program itself doesn't have any errors and compiles.
The .exe screen looks like this.

ERROR
Loading.....Done





Press any key to continue . . .

The program just doesn't seem to be finding "booklist2.txt." I put "booklist2.txt" in the same folder as the program's project folder. How can I make the presence of the file known to the compiler??
I'll be waiting for your kindly reply!!

Answer
FOLLOWUP:
---------------------------------------------------------------------------------

It occurred to me that I missed a few things you could do with regard to locating your data file:

1/ Look into the debug or debugger settings for your project (for example for VC6 under the Project Settings on the Debug tab). You might well be able to set the working directory used when debugging the application.

2/ You could use the full absolute path to your data file in your code.

3/ You could pass the file name (or file path) to the executable as a command line argument. In which case use the:

   int main( int argc, char * argv[] )
   {
   // ...
   }

form of main. Again you can probably set command line arguments to use when debugging in the debugging settings.

4/ You could ask the user for the file or file path to use.

3/ and 4/ are not so much direct solutions as ways to allow you to more easily try different paths for your file.

END OF FOLLOWUP
---------------------------------------------------------------------------------

I am guessing you are executing your program from within an integrated development environment (IDE) application such a Visual C++ or DevC++.

Different development environments place executables in different places and execute them from different working directories, with respect to the main project directory. This is the cause of the confusion (and yes it gets me from time to time as well). You might find you have to place your data file in the build directory where the executable is written to by the linker (e.g. in project\Debug for VC++, assuming you have performed a debug build).

The alternative is to open a command prompt (DOS box) window and change directory (cd) into the directory containing your data file and execute the application from within that directory. For example if your project data file was in the directory D:\projects\my_project and the executable in D:\projects\my_project\Debug then you would do something like the following:

   D:
   cd \projects\my_project
   .\Debug\my_project

You should also improve your error checking. Not opening the data file should almost certainly be a fatal error - you cannot sensibly continue without it being open! You can also check for a file being open using the is_open member function of std::fstream (and thereby of std::ifstream and std::ofstream). For example you could write:

  ifstream ins("booklist2.txt");

  if ( !ins.is_open() )
     {
     cerr << "ERROR, unable to open data file.\n";
         return 1;
     }

Note that there is _no_ standard signature of main that returns void, this is a Microsoft perversion. I suppose this implies you are most likely using VC6. The correct signature for main is:

   int main()
   {
   }

Also even though an int is indicated for the return type for main and main only a return statement is not required. If none is provided return 0 is assumed. I stress this is _only_ allowed for main, all other functions have to return a values unless they specify a void return type. Again VC6 gets this wrong so you have to add in a return 0 statement (or use their non-standard void main() signature).

You could also consider using a std::vector (include <vector>) instead of a built in array. In this context a vector is a single dimension array. In fact std::vector is a class template. You provide information on what type is held in the vector to create a specific vector type. The big plus is that std::vector types handle memory management for you and grow as you add more elements. However this means they do not automatically have any elements at all, so you typically call a member function called push_back to append data. In your case you would read the data into a local temporary Books object and then push_back the completed Books, which makes a copy of it at the end of the vector, extending it as necessary. In fact I think your Books type is in fact only a Book type. A collection of Book objects would be implied by the name Books!

   #include <vector>

   // ...

   // Dropped s at end of type name
   struct Book
   {
       string isbn;
       string title;
       string fn;
       string ln;
   };

   typedef vector<Book>    BookCollection;

   // ...

   int main ()
   {

       BookCollection bookList;

       // ...

       return 0;
   }

// The Book collection is now passed as a reference to a BookCollection
// type and the parameter renamed from b to books.
   void loadData (ifstream & in, BookCollection & books)
   {
       cout << "Loading";

       Book b; // b is now a single temporary Book object

       while ( in.good() )
       {
         in >> b.isbn;
         in.ignore(1);
         getline (in, b.title, '\t');
         in >> b.fn >> b.ln;

       // check all is well before adding to collection
         if ( in.good() )
         {
         books.push_back(b);
         }

         cout << '.';
       }

       cout << "Done\n";
   }

   // etc...


Things to note are that:

- I replaced all tabs with spaces. You seem to be mixing them. Don't. Use one or the other. I tend to convert tabs to spaces for indentation purposes as your code can look really badly mis-formatted if viewed or printed by an application which uses a different number of spaces per tab to those you used originally!

- I removed the s at the end of the Books type name. It is now just Book.

- I separated out the attributes of the Book struct one per line as this tends to be clearer, and easier to comment if necessary (you can add short comments next to each attribute if you wish).

- bookList is now a BookCollection type. BookCollection is a type alias for a std::vector<Book>, i.e. a vector of Book records. This means that loadData and showData have to be modified to accept this type: by reference for loadData (as it has to modify the contents) and by constant reference for showData, as you do not need to modify the contents but also do not wish to waste space and time copying the whole vector!

- As vectors grow as required there is no need for the size constant.

- I show a revised version of loadData that implements the scheme I previously mentioned. It also uses a while loop to keep on reading the records in the data file until there are no more (end of file is reached) or there is some other stream error. I'll leave the revised showData to you to ponder. You might wish to consider what to do if there is an error reading in the data in loadData rather than a natural termination by reaching the end of the file.

- You might like to know that std::vector types have a number of useful member functions and operators, such as size, which returns the number of elements in the vector and operator[] which allows element accesses similar to that for built-in arrays. For more information on std::vector and other C++ standard library facilities check out a good book such as "The C++ Standard Library A Tutorial and Reference" by Nicolai M. Josuttis (my copy is very well used!), or look for information online such as that at http://www.sgi.com/tech/stl/, although some material refers to non-standard facilities, or the papers at http://www.digilife.be/quickreferences/PT.htm.

- Strictly speaking some additional error handling needs to be added. If the program runs out of memory or some other nastiness occurs then types based on the likes of std::vector et al. tend to throw some sort of exception based on std::expception (include <exception>). If nothing else our top level code (i.e. the code in main) should be prepared to catch such exceptions:

   #include <exception>  // for std::exception
   #include <new>        // for std::bad_alloc

   // ...

   int main ()
   {
       try
       {

       // ...

       }
       catch ( bad_alloc & )
       {
         cerr << "ERROR: out of memory.\n";
       }
       catch ( exception & e)
       {
         cerr << "ERROR: C++ standard exception occurred.\n";
         << e.what() << '\n';
       }
       catch ( ... )
       {
         cerr << "ERROR: Unanticipated exception occurred.\n";
       }
   }

std::bad_alloc is derived from std::exception (i.e. std::bad_alloc is a std::exception) so we have to place the catch clause for it before std::exception otherwise the std::exception catch clause will handle std::bad_alloc exceptions as well. You can add further catch clauses as required. Which operations throw which exceptions is part of the documentation for the operation.

Next you may like to consider making Book a fully paid up class rather than just a grouping of data. You should consider what operations would be useful for Book, and whether they should be members or not. Note that data members (i.e. all members of the Book struct at present) should be private rather than public (the default for struct types).

For more on such things check out a good C++ text book / reference book or three. A very well referenced book for effective use of C++ is "Effective C++" by Scott Meyers. However you may not have covered all the material he mentions (or that I am talking about). Do not worry if you do not get it all at once. I just hope you can take away a few pointers (no pun intended!) to help you in the future if not now.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.