2.3 SIMPLE PROGRAMS: FILE IO


2.3 SIMPLE PROGRAMS: FILE I/O

Let's now compare programs in C, C++, and Java for copying one file into another file. The input file may be a text file or a binary file. We will first show the C implementation. We will assume that the executable for this program would be invoked with a command line like

    copy sourceFile destFile 

where copy is the name of the file containing the executable. Here is the source code:

 
/* FileCopy.c */ #include <stdio.h> #include <stdlib.h> main(int argc, char* argv[]) /* (A) */ { FILE *in, *out; /* (B) */ int ch; if ( argc != 3 ) { /* (C) */ fprintf( stderr, "usage: copy in_file out_file\n" ); exit( EXIT_FAILURE ); } if ( ( in = fopen(argv[1], "rb" ) ) == NULL ) { /* (D) */ fprintf( stderr, "Can't open %s\n", argv[1] ); exit( EXIT_FAILURE ); } if ( ( out = fopen(argv[2], "wb" ) ) == NULL ) { /* (E) */ fprintf( stderr, "Can't open %s\n", argv[2] ); fclose( in ); exit( EXIT_FAILURE ); } while ( ( ch = getc( in ) ) != EOF ) /* (F) */ if ( putc( ch, out ) == EOF ) /* (G) */ break; if ( ferror( in ) ) /* (H) */ printf( "Error while reading source file.\n" ); if ( ferror( out ) ) /* (I) */ printf( "Error while writing into dest file.\n" ); fclose( in ); /* (J) */ fclose( out ); /* (K) */ return 0; }

With the command line for invoking the executable of this program as shown previously, the parameter argc in line (A) will be set to 3 and the parameter argv will denote an array of character pointers, each pointer pointing to one of the strings in the command line. So the value stored in argv[0] will point to the string copy, the value stored in argv[1] to the string sourceFile, and the value stored in argv[2] to the string destFile.

In line (B), we declare two file pointers to serve as two I/O streams, each of type FILE*, a type declared in the header file stdio.h. We will use one of these, in, for reading the characters from a file and the other, out, for writing those same characters into another file.

Line (C) guarantees that the program will not inadvertently be called with a wrong number of command-line arguments given to copy. The macro EXIT_FAILURE is defined in the header stdlib.h with its value implementation dependent, although typically 1. The invocation of exit in the block that starts in line (C) will cause the program to terminate.

In line (D), we open the source file in the read binary mode.[15] The function fopen() returns a file pointer that becomes the value of in. In this line, we also ensure that the file is opened successfully by checking the value of the file pointer returned. If the file was not successfully opened, a message is sent to the standard error stream stderr. The block of code that begins in line (E) does the same for the destination file.

The actual job of copying from the source file to the destination file is done by the statements in lines (F) and (G). The getc() function gets one character at a time from the input stream, and the putc() function deposits the character into the output stream. If getc() returns EOF, it could be either because the end of file was reached, or because an error occurred during the process of reading the input stream.[16] Note also that we read what's returned by getc() into an int, since ch is of that type, and not into a char. This is to enable the detection of the EOF condition.

To make sure that if either the getc() or the putc() function returns EOF, it is not because of an error condition encountered, we test the error indicators associated with the two streams in lines (H) and (I). Finally, we close the streams in lines (J) and (K).

Shown below is a C++ program for doing the same thing—copying one file into another:

 
//FileCopy.cc #include <fstream> //(A) #include <cstdlib> using namespace std; //(B) void print_error(const char*, const char* = " "); //(C) int main(int argc, char* argv[]) //(D) { if (3 != argc) print_error("usage: copy source dest"); ifstream in( argv[1], ios::binary ); //(E) if (!in) print_error( "can't open", argv[1] ); ofstream out( argv[2], ios::binary ); //(F) if (!out) print_error( "can't open", argv[2] ); char ch; //(G) while ( in.get(ch) ) //(H) out.put( ch ); //(I) if ( !in.eof() ) //(J) print_error("something strange happened"); return 0; } void print_error(const char* p, const char* p2) { //(K) cerr << p << ' ' << p2 << '\n'; //(L) exit(1); //(M) }

In line (A), we include the header file fstream.[17] This file includes the classes ifstream and ofstream that are needed for setting up the input and the output streams. The using directive in line (B) takes care of the fact that the identifiers used in the header fstream are defined in the namespace std.[18]

Line (C) declares the function prototype for the print_error function. This function has a default argument for its second parameter, the default argument being the empty string.[19]

The header of main() in line (D) is the same as the header of the C program shown before. Then, after we make sure that the program is called with the correct number of command line arguments, in line (E) we create an input stream by making an object named in of class ifstream. The file name associated with this object is the string pointed to by the pointer argv[1]. The object in will evaluate to false if the file cannot be opened for some reason. Similarly, in line (F) we construct an output stream object named out for the destination file in a manner similar to what was done for the input stream object.[20]

The actual file-to-file copy takes place in lines (H) and (I). In line (G), the function get() returns the iostream stream object on which the function is invoked, unless it has reached the end of the file, in which case the invocation get returns false.[21]

Line (J) checks the state of the input stream object. The function eof() will return true if the stream to which it is applied has encountered the end of file. So, if the flow of control has reached line (J) without the input stream having reached the end of the file, !eof() will return true and the error message in line (K) will be printed out on the standard terminal.

That leaves us with the definition of the print_error() function in line (K). The only thing new here is the cerr object in line (L) for printing out the error messages on the user's terminal. While the object cout introduced in the previous section defines the standard output stream, the object cerr defines the standard error stream. Most shells allow the standard output and the standard error streams to be redirected independently. You could, for example, redirect the standard output into a file or a pipe while the standard error keeps on going to the user's terminal or into some log file. The invocation of exit() in line (M) causes immediate program termination with its argument as the value that is returned by the program to the operating system. A value of 0 means that the program finished successfully without encountering any error conditions.[22]

The reader will note that, unlike in the C program, we did not explicitly close the streams. In C++, as the streams go out of scope, their destructors are invoked; the destructors automatically close the streams. However, there do arise situations when we may need to explicitly close a stream. This can be done by invoking the function close() on the stream. For example, if we wanted to close the in input stream, we would say

    in.close(); 

We will now show a Java program that does the same thing:

 
//FileCopy.java import java.io.*; //(A) class FileCopy { //(B) public static void main( String[] args ) //(C) { int ch = 0; FileInputStream in = null; //(D) FileOutputStream out = null; //(E) if ( args.length != 2 ) { //(F) System.err.println( "usage: java FileCopy source dest" ); System.exit( 0 ); } try { in = new FileInputStream( args[0] ); //(G) out = new FileOutputStream( args[1] ); //(H) while ( true ) { ch = in.read(); //(I) if (ch == -1) break; out.write(ch); //(J) } out.close(); //(K) in.close(); //(L) } catch (IOException e) { System.out.println( "IO error" ); } } }

In line (A), we import the package java.io because it contains the classes we need to create the input and output streams. Since a function in Java can reside only inside classes, in line (B) we define a class FileCopy inside which we will write the file copying function. The syntax in line (C) is the same as for the main() function in Java shown before, except that now the parameter args will actually be bound to an array of Strings corresponding to our command line arguments. We will use the following command line invocation of this program:

    java fileIODemo sourceFile destinationFile 

With this command line invocation, args[0] will be bound to the String sourceFile and args[1] to destFile.

With the declarations in lines (D) and (E), in can be used as an input stream and out as an output stream. The input and the output streams are bound to the respective files in lines (G) and (H). The function read() associated with the class FileInputStream reads one byte at a time in line (I). And the function write() writes that byte into the output stream in line (J). In between, we check whether or not the input stream has encountered the end of the source file. Finally, we close the two streams in lines (K) and (L).

In the program shown, setting up of the input and output streams and reading the input stream and writing to the output stream have all been carried out inside a try—catch block. This is necessitated by the fact that these Java methods are capable of throwing an exception of type IOException if something were to go awry during their execution. In Java, if an invoked function is capable of throwing an exception, then that exception must either be caught with a catch block or the calling function must rethrow the exception.[23]

[15]The undesirable consequences of reading a binary file in character mode are discussed in Section 6.8.3.

[16]To refresh the memory of the reader, EOF, a macro defined in the header file <stdio.h>, is a negative integer constant, usually—1.

[17]The fstream header file includes the iostream header file. See Chapter 6 for the C++ iostream library.

[18]Namespaces are discussed in Chapter 3.

[19]The default arguments feature of C++ will be discussed in Chapter 9. Suffice it here to say that we have the choice of calling the function print_error() with only one argument. That argument will become the value of the first parameter. The second parameter in such a case will be set to an empty string.

[20]The classes ifstream and ofstream are subclasses of the class iostream. They inherit all the public attributes of the parent class iostream and in addition have their own specialized attributes for file I/O. See Chapter 6 for a treatment of the C++ iostream library.

[21]An alternative would be to use the following invocation in line (G):

    int ch = in.get(); 

since at the end of the file the function get() returns EOF that is defined in the header file iostream to be—1. Chapter 6 presents further details.

[22]The execution of a C++ program can also be terminated immediately by calling abort(). The advantage of exit() is that it can close any open output streams and invoke the destructors for any constructed static objects before actually terminating the program. As we will see in Chapter 10, the execution of a program can also be terminated by throwing an exception that is not caught by the program. For large and complex OO programs, throwing an exception is the way to go if immediate termination of program execution is needed. The reasons for this will become clear after the reader has gone through Chapter 10.

[23]As we will explain in Chapter 10, this requirement applies strictly to what are known as checked exceptions.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net