C for Yourself - part 12

Steve Mumford continues his investigation of the methods of input and output.

Last month we looked at the functions printf() and scanf(), along with the basics of file handling. I'll fill in some more of the details this month, and I'll start by introducing a couple of functions that are specifically for printing strings.

gets() and puts()

Although you can use scanf() to input a string from the user, it's probably not the best function for the job since it will stop reading after it finds a white space character - in most cases you'll end up with only the first word stored in memory.

This isn't particularly useful as a method for entering long strings, so another function was devised - gets(). Before you can use it, you must have defined an area of memory that is large enough to store the string:

char string[100];

The first line sets aside 100 bytes of memory for a character array, and points string[] to the start of that block. The gets function takes a pointer to an area of memory as a parameter, and stores the string starting at that address. Input is read from the keyboard until a carriage return character is detected, so this routine doesn't suffer the truncation problems of scanf(). The return character is not stored in the string, but the function does tack on a zero byte termination character to replace it.

However, it's still possible to run into problems. For instance, if you had only reserved ten bytes of memory and then attempted to store a string which was longer than that, sections of memory immediately after the array would be overwritten, probably culminating in a program crash. It's possible to include safeguards to protect against this sort of error, but it's still vital to remember the termination character - an array which is ten bytes in size can only hold a nine-character string, since the tenth character is needed for the string's zero byte.

In order to complement gets(), a function called puts() is supplied as part of the standard library:

char *string = "Hello, World";
puts("This is some text.");

It's simpler to use than printf(), but it doesn't allow you to print variables. Note that you don't need to include carriage returns at the end of the strings when using puts() - that's because the zero byte at the end of the string is translated into a return character when it is printed.

Loading and Saving

Last month we looked at the techniques of opening and closing a file, but we didn't get round to doing anything with it. I'll have to introduce two new routines before we go any further, but due to some of the philosophies behind C, they bear a striking resemblance to functions you've already met. C treats all input and output as different files, so you can write to the screen, the printer or a disc in the same general fashion.

printf() automatically sends its output to a 'file' called stdout, and scanf() takes input from stdin - these are usually mapped onto the screen and the keyboard respectively, unless someone's crept up to your machine and redirected them. Because these 'files' are used so often, they're provided as standard by the stdio.h library, and they're always kept open. When writing to a disc you need to create your own file before you can use it, and that's what we were doing last month.

Since the destinations of printf() and scanf() can't be altered easily, we need new functions that take another parameter - one that specifies where the information is stored. We'll have a look at fprintf() first, which takes the same parameters as printf() with the addition of the extra destination argument:

FILE *pointer;
char *name = "filename";

pointer = fopen(name, "w"); fprintf(pointer, "Writing to file: %s\n", name); fclose(pointer);

Last month, I briefly mentioned the modes with which you could access a file, but I didn't have space to explain how you specified which mode you wished to use. Firstly, you must decide whether you want to read from the file, write to it, or append data onto the end of it. These actions are given the letters r, w and a respectively. However, this is still somewhat restrictive, because in some cases you may want to perform both operations at once. An extra mode is suppled to deal with this, called update, and if you wish to use this you should add a + symbol to your chosen mode. Finally, you can specifywhether the output is in text or binary format - the difference between the two is that the text mode only contains the subset of printable characters, and numbers are stored in their textual representation. In binary mode, the full range of ASCII characters can be saved and numbers can be stored in the machine's internal format. If you wish to use this binary mode you include a b in the mode string. A point to note is that the mode r+ opens an existing file for update, whereas the mode w+ creates one, erasing any files of the same name if they're already present.

Therefore, in order to save three numbers, the code might look something like this:

fprintf(pointer, "%d %f %d\n", var1, var2, var3);

This saves an int, a float and another int in sequence to the file pointed to by pointer. It's important that the numbers are separated by a white space character - otherwise there's no way to distinguish between them when you read the numbers back in. In order to do the reading, you could use the line:

fscanf(pointer, "%d %f %d\n", &var1, &var2, &var3);

fscanf() can actually return an integer value if required, and this corresponds to the number of data items successfully loaded. In the above example, there are three data items to be retrieved - if scanf() returned with a value less than three, it indicates there was a problem with the load. When used in this way, the form of the command is similar to that shown below.

int n;
n = fscanf(pointer, "%d %f", &var1, &var2);

When reading data, it's important to be able to check whether you've reached the end of the file, and that function is provided by feof(). This returns the value zero if the end of the file hasn't been reached, and a non-zero value if it has. The following code fragment reads a list of integers from a file pointed to by handle and stores them in an array. The loop continues until the program discovers the end of the file:

loop = 0;
while (feof(handle) == 0)
  fscanf(handle, "%d", &(array[loop]));

The file handling we've done so far has been sequential - we've not been jumping about in the file while we've been using it. However, C provides facilities for random access which can be particularly useful if the file you're using is fairly large and you can't afford to keep all of it in memory at once. I'll have a look at these next time, and mention some of the more advanced methods of storing data in memory.

Sidebox: "A few conversion specifiers for printf()"

You've already met a few of the conversion specifiers that can be used in the control string of the printf() function - here's a more comprehensive list. Strictly speaking, if you want to print the 'long' forms of int or float, you should add a lower-case l after the % symbol, using the forms %ld or %lf. Although not so important for printf(), it's vital when reading long ints and doubles as input, so the precision of the data isn't lost.

Source: Acorn User - 156 - June 1995
Publication: Acorn User
Contributor: Steve Mumford