Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

232 Technique 41: Reading In and Processing Files

Notice the operators that are defined in this block of code (shown at the lines marked 3 and 4). These provide a standard way to retrieve a single line of a file into a string object — and a fast, efficient way to remove a character from a string. The nicest thing about C++ operators is that they can be defined externally to the class they manipulate, so they don’t require that the original classes be modified. This is a good way to extend functionality without derivation or modification.

Testing the File-Reading Code

After you have created the file-reading functionality, you should create a test driver that not only ensures that your code is correct, but also shows people how to use your code.

The following steps show you how to create a test driver that illustrates how the two methods for inputting data (OpenAndReadFileOld and OpenAndReadFileNew) are used — and what the output of each will be.

1. In the code editor of your choice, reopen

the existing file to hold the code for your test program.

In this example, I named the test program ch 41.cpp.

2. Append the code from Listing 41-3 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 41-3: THE FILE-READ TEST DRIVER

int main( int argc, char **argv )

{

if ( argc < 2 )

{

printf(“Usage ch5_3 filename\n”); exit(1);

}

 

Entry

entries1[50];

int

num = 0;

printf(“Old Way:\n”);

if ( OpenFileAndReadOld( argv[1], entries1, 50, &num ) == false )

{

printf(“Error processing file %s\n”, argv[1] );

exit(1);

}

for ( int i=0; i<num; ++i )

{

cout << “Entry “ << i << endl; cout << “Name: “ << entries1[i].getName() << endl; cout << “Value: “ << entries1[i].getValue() << endl;

}

printf(“New Way:\n”); std::vector< Entry > entries2; if ( OpenFileAndReadNew( argv[1],

entries2 ) == false )

{

printf(“Error processing file %s\n”, argv[1] );

exit(1);

}

std::vector< Entry >::iterator iter; int nPos = 0;

for ( iter = entries2.begin(); iter != entries2.end(); ++iter )

{

cout << “Entry “ << nPos << endl; cout << “Name: “ << (*iter).getName() << endl;

cout << “Value: “ << (*iter).getValue() << endl; nPos ++;

}

}

This simple program just allows us to read in a test file in two different ways, using both the oldstyle C functions and the new-style stream functions. We then print out the values to compare them. Note that the old-style function requires that we use a fixed-size array, while the stream version uses the newer vector class to allow for an almost-infinite number of entries.

3. Save the source-code file and close the source editor application.

Creating the Test File 233

Creating the Test File

In order to use the test driver, we need some input data for the driver to process. Let’s create a simple test file that contains data that we can read in to compare the old-and new-style functions of input and output.

1. In the code editor of your choice, create a text file for testing the application.

In this case, I used the name test.txt for the test file.

2. Type the following text into the test file:

Config1=A config string

Config2=100

Config3=Another line

# This is a comment Config4=A.$5

3. Save the test file and close the code-editor application.

4. Compile and run the program with your favorite compiler and operating system.

If you have done everything properly, you should see the following output from the program on your console window:

$ ./a.exe test.txt

 

5

Old Way:

 

Entry 0

 

Name: Config1

Value: A config string

Entry 1

Name: Config2

Value: 100

 

 

Entry 2

 

 

Name: Config3

 

 

Value: Another line

 

 

Entry 3

 

 

Name: Config4

 

 

Value: A.$5

 

6

New Way:

 

Entry 0

 

Name: Config1

Value: A config string

Entry 1

Name: Config2

Value: 100

Entry 2

Name: Config3

Value: Another line

Entry 3

Name: Config4

Value: A.$5

As you can see by the output of our program, both the old and new ways of processing the data work the same. All of the entries shown under the Old Way output line were read in using the old-style C functions, while the output shown under the New Way output line were read in using the new-style streams. By using this simple program, therefore, we can easily convert old-style applications to the new-style functions quickly and easily, saving time and effort.

No functional difference exists between the old-style C functions and the new-style C++ streams. To see this, compare the lines shown at 5 and at 6. Leaving out the additional operators that can be reused anywhere, however, makes the new-style code that handles streams considerably smaller and easier to read; by comparison, the old-style code is cumbersome and confusing.

42 How to Read Delimited Files

Technique

Save Time By

Understanding standard delimited files

Creating generic classes for reading or loading standard delimited files into your application

Testing your output

There are a lot of ways to store data in file systems. One of the most popular is a standard delimited file, in which the individual fields of the records are separated by known delimiters. There are numer-

ous examples of this, from comma separated values (CSV) to XML files to fixed-size records that include null bytes. The capability to load delimited data into your application is very valuable — and it makes your application considerably easier to use.

Extracting the input and output of data formats into separate classes will not only make the classes more reusable across applications, it will also save you lots of time when trying to load known formats into new applications.

This technique builds some generic classes that aid loading and parsing delimited files. I have to make a few assumptions here, although each of these assumptions is fairly easy to adapt if you need to change the logic. The assumptions are as follows:

The files contain only delimiters that separate fields. That is, no fields in the file contain delimiters.

The records exist one per line. This is really just a convenience; it would be easy enough to check for an end-of-record signature other than the end-of-line character.

The fields can vary in size.

Reading Delimited Files

Because the need to read delimited files is such a common problem in the software development world, it makes sense to build a generic method for reading them. By doing this, we save a lot of time because we are able to move the code to read the files from project to project. For example, you can create a generic class that can be used to read any sort of delimited files and test it with a variety of input.

Reading Delimited Files 235

1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.

In this example, the file is named ch42.cpp, although you can use whatever you choose.

2. Type the code from Listing 42-1 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 42-1: READING A DELIMITED FILE

#include <stdio.h> #include <string> #include <vector> #include <iostream> #include <fstream>

//Avoid having to type out std:: for all

//STL classes.

using namespace std;

// This class manages a list of delimiters.

1

class Delimiters

 

{

 

private:

//Array of possible delimiters. Use a vector,

//since the characters could include NULL byte

//or other characters that string can’t handle.

vector< char > _delimiters;

protected:

virtual void Copy ( const Delimiters& aCopy )

{

vector<char>::const_iterator iter; for ( iter =

aCopy._delimiters.begin(); iter != aCopy._delimiters.end(); ++iter )

_delimiters.insert( _delimiters. end(), (*iter) );

}

public:

Delimiters(void)

{

}

Delimiters( const char *delimiterList )

{

for ( int i=0; i<strlen(delimiterList); ++i)

{

_delimiters.insert( _delimiters. end(), delimiterList[i] );

}

}

Delimiters( const Delimiters& aCopy )

{

Copy ( aCopy );

}

Delimiters operator=( const Delimiters& aCopy )

{

Copy( aCopy);

}

virtual ~Delimiters(void)

{

}

//Clear out the entire list. virtual void Clear()

{

_delimiters.erase( _delimiters. begin(), _delimiters.end() );

}

//Add a delimiter to the list. virtual void Add( char c )

{

_delimiters.insert( _delimiters. end(), c );

}

//See whether a given character is in the list.

virtual bool Contains( char c )

{

vector<char>::const_iterator iter; for ( iter = _delimiters.begin(); iter != _delimiters.end(); ++iter )

if ( c == (*iter) ) return true;

return false;

}

// Remove a given delimiter. virtual bool Remove( char c )

{

vector<char>::iterator iter;

for ( iter = _delimiters.begin(); iter != _delimiters.end(); ++iter )

if ( c == (*iter) )

{

(continued)

236 Technique 42: How to Read Delimited Files

LISTING 42-1 (continued)

_delimiters.erase( iter ); return true;

}

return false;

}

};

//This class manages the data for a given row of a

//delimited file.

class DelimitedRow

 

2

{

 

 

private:

 

 

 

vector<

string > _columns;

 

 

protected:

 

 

 

virtual

void Copy( const DelimitedRow& aCopy )

{

 

 

 

vector<string>::const_iterator iter;

for

( iter = aCopy._columns.begin(); iter != aCopy._columns.end(); ++iter )

 

_columns.insert( _columns.end(), (*iter) );

}

public:

DelimitedRow(void)

{

}

DelimitedRow( const char *col )

{

Add( col );

}

virtual ~DelimitedRow()

{

}

virtual void Add( const char *col )

{

_columns.insert( _columns.end(), col );

}

int NumColumns( void )

{

return _columns.size();

}

string getColumn( int index )

{

if ( index < 0 || index > NumColumns()-1 ) return string(“”);

return _columns[index];

}

};

Reading Delimited Files 237

// This class will handle a single delimited file.

class DelimitedFileParser

 

3

{

 

 

private:

 

 

 

string

 

_fileName;

 

Delimiters

 

_delim;

 

ifstream

 

_in;

 

vector<DelimitedRow>

_rows;

 

protected:

 

 

 

virtual void Copy( const DelimitedFileParser& aCopy )

{

 

 

 

_fileName = aCopy._fileName;

 

_delim

= aCopy._delim;

 

vector<DelimitedRow>::const_iterator iter;

for ( iter = aCopy._rows.begin(); iter != aCopy._rows.end(); ++iter ) _rows.insert( _rows.end(), (*iter) );

}

virtual void _ParseLine( string sIn )

{

//Given a delimiter list, and an input string, parse through

//the string.

DelimitedRow row; string sCol = “”;

for ( int i=0; i<sIn.length(); ++i)

{

if ( _delim.Contains( sIn[i] ) )

{

row.Add( sCol.c_str() ); sCol = “”;

}

else

sCol += sIn[i];

}

row.Add( sCol.c_str() ); _rows.insert( _rows.end(), row );

}

public:

DelimitedFileParser(void)

{

}

DelimitedFileParser( const char *fileName, const char *delimiters ) : _delim( delimiters )

{

Open( fileName );

}

DelimitedFileParser( const DelimitedFileParser& aCopy )

{

Copy ( aCopy );

}

(continued)