Objects First |
| Streams | The first group operates on streams, so called because they usually consist of continuous streams of characters, often without a clearly defined end. The characters that I'm typing on this keyboard are a typical stream. Similarly the stream of characters directed to an output window is a typical stream. These two streams have no clearly defined end: I use various separator characters to designate the end of groups of characters which are related - but the separator is itself a character and the stream continues immediately after the separator. Streams usually consist of text data: some of the library functions that operate on streams are designed to read variable length lines of text delimited by new-line or carriage return characters. |
|---|---|
| Binary I/O | The second group provides raw binary I/O to a device. The functions in this group handle most other other types of I/O: they work efficiently when reading or writing fixed length blocks of data from files or other devices. |
#include <stdio.h> int fputc( char c, FILE *f ); int fgetc( FILE *f );These specifications, along with most in this section, are found in the standard header file stdio.h. |
Observe the standard practice for C library documentation: the specification file for the library or class is included as part of the formal specification of the function as seen in this example. You add to your program the #include ... statement - it will serve to import all the function specifications into your code. |
| The FILE type is also defined
in stdio.h.
The need to designate file handles as pointers, ie to write FILE *, is a historical hangover from some less than ideal decisions made by the original designers of C in 1970 or so. It would have been possible, and neater, to define FILE as a pointer itself (in exactly the same way that we have been defining classes in this course!). Unfortunately, Object Oriented techniques hadn't been widely recognised as useful as early as that. Now there is so much existing C code containing FILE * that it's too late to go back! |
If you look in stdio.h, you will
see a declaration like:
typedef struct io_buf {
....
} FILE;
Inside the struct you will see all the attributes that the
C stream functions need to interact with streams.
|
#include <stdio.h>
void cat() {
char c;
while( TRUE ) {
c = fgetc( stdin );
fputc( c, stdout );
}
}
This function is fine for a program that runs forever on an infinite
stream of data, but not too good for real streams.
A better version is:
#include <stdio.h>
void cat() {
char c;
while( (c = fgetc(stdin)) != EOF ) {
fputc( c, stdout );
}
}
First examine the assignment expression and verify that this
works: the expression
(c = fgetc(stdin))
But what's the value of EOF? If we look in the ASCII code tables, we will see that all 256 possible values of an eight-bit character are assigned some meaning. Look back and note that fgetc is an int function - not a char one as we might expect! char objects can take values from 0 to 255, leaving a rather large number of choices for EOF which do not conflict with the 256 possible char values. Thus when fgetc finds an end on the input stream (as it might if it was reading a file), it returns EOF (often assigned the value -1), rather than a value in (0,255) which could be interpreted as a char.
int getchar(void);which is exactly equivalent to fgetc( stdin ). In fact, if you look in stdio.h, you will often find it defined in exactly that way:
#define getchar() fgetc(stdin)which means that when you type getchar() in your program, the compiler substitutes fgetc(stdin). (The C pre-processor, which performs these substitution operations is discussed fully in a later section.) There is a similar shorthand for fputc :
int putchar(char c);which is equivalent to fputc(c, stdin) and is also often implemented as a macro.
char *fgets( char *s, int n, FILE *stream ); /* stdin abbreviation */ char *gets( char *s ); int fputs( char *s, FILE *stream ); /* stdout abbreviation */ int puts( char *s );These routines work with null-terminated strings, with some additional quirks. fgets reads characters from stream until it finds a null character, a newline or an EOF or until it has read n characters. The string read is placed in s which, naturally, must be either a char array with at least n elements or a pointer to at least n bytes allocated by malloc. As with all string functions, care must be exercised: fgets will store a null character at the end of the string stored in s (unlike some of the other string routines) but it will not read all of a string which has more than n-1 characters.
fputs, as you might expect, writes the string, s to the stream until it encounters the NULL at the end of the string. It will return an error (-1) if there is insufficient space or the stream experiences another error.
However, when reading from text files generated by other C programs - so that the format of the lines in the file is carefully controlled, then fscanf, etc become a little more useful. This is because you're dealing with well-known input and don't have to worry about a user's concept of what might be 'reasonable' input for the program! Users tend to have a very broad definition of 'reasonable' which is often at considerable variance to fscanf's idea of reasonable!
FILE *fopen( char *filename, char *mode ); int fclose( FILE *stream ); int feof( FILE *stream ); long int ftell( FILE *stream );You open a file by passing its name to fopen:
#include <stdio.h>
..
FILE *f;
char *filename = "filex.dat";
f = fopen( filename, "r" );
if ( f != NULL ) {
/* File successfully opened */
.... /* Various operations on the file */
fclose( f );
}
else {
fprintf( stderr, "Error opening file %s\n", filename );
}
The second argument to fopen is the mode in which you want
to open the file: it is a string
(formally the type is char *)
- even though the most used values are single characters!
| Operation on file | Value for mode |
|---|---|
| read | "r" |
| write a new file | "w" |
| append to an existing file | "a" |
| reading and writing, starting at beginning | "r+" |
| reading and writing, truncate existing file | "w+" |
| reading and writing, append to existing file | "a+" |
ftell returns your position in the file - measured in bytes from the beginning. Thus when you open a file at the beginning, ftell returns 0. After you have read n bytes, it will return n.
There is also a function fseek which enables you to move backwards in a file. It will let you move forwards also, but you can do that by reading the file! Use of fseek is not usually consistent with the type of file (text containing lines of variable length) for which the stream I/O functions are best suited - and some types of streams (eg those coming from communications devices, your keyboard, etc) have strictly limited capacity to be re-wound. Calling fseek to ask the OS to wind back the keyboard device by 106 characters isn't likely to be possible - a typical OS will buffer stream input until it is consumed and no longer! However it has use on files on rare occasions: it behaves like lseek - look it up in your text if you ever need it!
Key terms |
|
Continue on to Binary Input and Output Back to the Table of Contents |