To do this, instead of using printf for output, we will use fprintf (i.e. file printf) and instead of using scanf for input, we will use fscanf (i.e. file scanf).
Calls to these functions will look very similar to the way we already use printf and scanf, but with one extra parameter controlling the file we are reading from or writing to.
The extra parameter that controls access to a file is called a file pointer, as it points to our current location in a file as we read our way through it or write content into it.
The general sequence we follow whenever we work on a file is as follows:
The C++ syntax for these steps is as follows:
FILE* fpin; // will use fpin for an input file - one we will read from FILE* fpout; // will use fpout for an output file - one we will write to fpin = fopen("someDataFile.txt", "r"); // open the file named someDataFile.txt fpout = fopen(filename, "w"); // open the file whose name was stored earlier // in a char array called filename |
FILE *fpin; fpin = fopen("myfile.cpp", "r"); // here we want to read from a file named myfile.cpp if (fpin == NULL) { // it didn't work printf("Sorry, we could not open file myfile.cpp for reading\n"); } |
// fscanf is like scanf, but we add the file pointer first, e.g. fscanf(fp, "%d", &i); // read an int from the file into variable i // fgets works as discussed previously, but we provide the file pointer instead of stdin: fgets(array, maxsize, fpin); // read one line from the file into the array, up to the maximum size // getc works as discussed previously, but we use the file pointer as a parameter: char c = getc(fpin); // there is also an ungetc, that puts a char BACK into the input buffer: ungetc(c, fpin); |
// try to open the file for reading FILE* fpin = fopen("somefile", "r"); if (fpin == NULL) { printf("Could not open the file\n"); } else { // read one character at a time from the input file (using getc), // displaying them on the screen as we go (a regular printf), // and finishing when we've reached the end of the file do { char nextc = getc(fpin); if (!feof(fpin)) { printf("%c", nextc); } } while (!feof(fpin)); } |
Similarly, we could try reading a file one line at a time, until the end of file
// assuming the file is open for writing, // and const MaxLineLength is declared char nextline[MaxLineLength]; do { fgets(nextline, MaxLineLength, fpin); if (!feof(fpin)) { // do whatever with nextline } } while (!feof(fpin)); |
// assuming the file is open for writing and fprintf(fpout, "blah blah %d blah %f", 10, 25.678); |
fclose(fpin); // or fpout, or whatever you called your FILE* variable |
The three modes for doing so are "r+", "w+", and "a+". All three open for both reading and writing, but r+ requires the file already exist whereas w+ creates a new file if it doesn't already exists, and append of course adds new content to the end of the existing file.
We can "jump" from location to location within a file by specifying
the where to jump from and how far to jump:
fseek(fp, N, SEEK_SET) jumps to the spot N characters from the start of the file
fseek(fp, N, SEEK_END) jumps to the N characters from the end
fseek(fp, N, SEEK_CUR) jumps N characters from the current spot
ftell(fp) tells us our current position (characters from the start)
File pointers as parameters
Sometimes we want to open a file pointer in one function, pass it as a parameter to another function, and have the called function read/write contents.
To do this, we must make sure the file pointer is passed-by-reference, and it is a good idea for the function to test the file pointer it receives to ensure it really is open, e.g.
#include <cstdio> // function to write an integer value into a file, // the file should have been opened for output earlier void writeNumberIntoFile(FILE* fp, int N); int main() { // attempt to open a file for writing FILE* fpout = fopen("somefile", "w"); if (fpout == NULL) { printf("Could not open file\n"); } else { // get the function to write value 123 into the file writeNumberIntoFile(fpout, 123); // close the file when we're done fclose(fpout); } return 0; } // function to write an integer value into a file, // the file should have been opened for output earlier void writeNumberIntoFile(FILE* fp, int N) { if (fp != NULL) { fprintf(fp, "%d\n", N); } } |
Binary I/O
Everything above assumes plain text files are being processed, but sometimes we want to manipulate binary files (images, executables, etc).
To do so, we use binary mode, adding the letter b to our chosen mode, e.g. "rb", "rb+", "wb", etc.
We can then write binary variable contents directly into the file or read the file contents directly into variables (assuming we know the layout of the file content).
To read from the file, we specfiy where to put the data being read,
how big each data element is, how many data elements there are, and
the file pointer to read from, e.g. assuming i is an int:
fread(&i, sizeof(int), 1, fp);
Similarly, to write variable contents into the file we use fwrite:
fwrite(i, sizeof(int), 1, fp);