CS 240 printf FAQ

printf() is a complicated function and many students approach it with a great deal of trepidation, armed with a variety of examples in the hope that they'll find one that fits the task that they've been directed to perform. The goal of this page is to give you enough information to know how to approach a job that calls for printf() and be able to construct a solution rather than have to search for an example. We want you to understand how printf() works.

The basics

printf(), at the very minimum, takes one argument--the string to be printed.

[1]        printf("Hello, world!\n");

Typically, we add a \n character at the end of the string in order to print a newline character. We don't have to add a newline. If we don't, any subsequent printf() operation will continue at the place where the previous one left off on the same line. For instance, the following example:

[2]        printf("Hello,");
           printf(" world!\n");

is equivalent to example [1].

We can take this to an extreme...

[3]        printf("H");
           printf("e");
           printf("l");
           printf("l");
           printf("o");
           printf(",");
           printf(" ");
           printf("W");
           printf("o");
           printf("r");
           printf("l");
           printf("d");
           printf("\n");

Examples [1], [2] and [3] all have the same effect but [1] is far more easy to type and is a great deal more efficient for the computer than [3].

Special clue: Note that \n cannot be split up into two separate characters. There are a number of special characters that are formed from the \ character followed by another letter. Another example is the "tab" character that is represented by \t. In many cases, we could just put the character in question right into the string. So the following examples are equivalent:

[4]        printf("	One tab.");
           printf("\tOne tab.");

           printf("This line ends with a newline.
");
           printf("This line ends with a newline.\n");

Note that some compilers really don't like seeing a quoted string split across two lines. It's a lot cleaner to use the synthetic characters. Here's a partial list of the interesting synthetic characters:

Synthetic character ASCII code Effect
\0 0 NUL character
\t 9 horizontal tab
\n 13 newline
\\ 92 backslash
\" 34 double quote

If you're curious about these ASCII codes, read the ASCII FAQ.


printf arguments

As a string printer, printf() does a fine job but it becomes far more useful when we introduce the concept of substitution arguments. Each substitution variable is referred to using a % prefix. By combining lots of substitutions, we can tailor our use of printf to render lots of data in a particular format. In fact, the string we've seen so far is called the format string because of the role that it plays.

The simplest example might be something like the following:

[5]        printf("There are %d apples for sale.", 450);
Here, the sole substitution variable is %d and exists in the center of the format string. Naturally, it would print something that would look like:
           There are 450 apples for sale.

And we can have a printf() statement that includes several arguments.

[6]        printf("The numbers are %d, %d and %d.\n", num1, num2, num3);

Assuming that the values for num1, num2 and num3 were 15, 8 and 99, respectively, the statement in example [6] would print:

           The numbers are 15, 8 and 9.

There is no theoretical limit to the number of substitution variables that a printf() can use. However, practically, you'll want to keep the number small to avoid bugs and maintain your sanity. Keep in mind that you must always have the same number of data arguments as you have substutition variables in the format statement.


Type of substitution arguments

As one would expect, there are many more types of substitution arguments than just %d for (d)ecimal integers. There are conversion specifiers that are widely accepted in varying implementations of printf().
d,iInteger
uUnsigned integer
oOctal integer
x,XHexadecimal integer
e,EFloating-point number with exponent
f,FFloating-point number with no exponent
g,GFloating-point number with no exponent
a,A"Long double" floating point number
cA single character
sA string
pThe value of a pointer
nThe number of characters printed so far
%A percent sign

So one could use something like the following to print a string:

[7]        printf("%s", "Hello, World!\n");

And, again, example [7] produces the same result as example [1].

These conversion specifiers can be combined to print a variety of things but to truly make the output look nice, we need to learn about a few other flags and length indicators that customize the output. First, look at the flag characters.

The flag character appears in a substitution variable specification between the % and conversion specifier character. For instance, if we wanted to always print the sign of an integer, we might use a specification like:

[8]        printf("The number is %+d.\n", num);

A list of the common flags follows:

0Pad the left side of a numeric argument with zero.
-Left justify a string argument.
Pad the left side of an argument with space.
+Always indicate the sign of a numeric argument.

The field width of the substitution can be indicated by a number between the % and conversion specifier (after the flag character, if one exists). For instance, to print an integer that is exactly 7 digits wide with zeros padded to the left, one would use a format like this:

[8]        printf("The number is %07d.\n", num);

Note that example [8] includes both a field width (7) and flag character (0).


Floating point precision

Field width for floating point numbers can also be customized to say how much space should be allocated for the part of the number on the right side of the decimal point. This is done by adding a ".number" after the field width. For instance, the substitution variable %+5.3f would always leave space for the sign, five more characters for the floating point value with three of those spaces reserved for the decimal part.


Putting it all together

Suppose we have a section of a program in which we need to print multiple lines that each contain a string and an integer of varying length. We want it to be formatted nicely but if we just use basic conversion specifiers, like this:

[9]        for ( num = 0; num < maximum; num++ )
           {
             printf("%s has %d marbles.\n",
               name_array[num],
               count_array[num]);
           }

The output might look something like this:

           Rufus Xavier Sassaparillo has 869873465 marbles.
           Rick has 0 marbles.
           Bartholomew has 993932934 marbles.
           Christopher has 1492 marbles.

So we analyze the data and decide that the names should all be 25 characters wide, left-justified, and the integers should be 10 characters wide, right-justified. We change the printf() statement like this:

[10]       for ( num = 0; num < maximum; num++ )
           {
             printf("%-25s has % 10d marbles.\n",
               name_array[num],
               count_array[num]);
           }

And the new output should look something like this:

           Rufus Xavier Sassaparillo has  869873465 marbles.
           Rick                      has          0 marbles.
           Bartholomew               has  993932934 marbles.
           Christopher               has       1492 marbles.

printf's friends

There are two functions that work very much like printf() but on different types of outputs. They are sprintf() and fprintf(). Their prototypes are as follows:

           int printf( char *format, ...);
           int fprintf( FILE *file_pointer, char *format, ...);
           int sprintf( char *buffer, char *format, ...);

Basically, fprintf() is just like printf() except that it sends its output to the FILE pointer specified in the first function argument. sprintf() is the same as printf() except that it sends its output into a string buffer that is specified as the first function argument. For example, you might do something like this with sprintf():

[11]       #include 

           int main()
           {
             char buffer[1000];

             sprintf ( buffer, "%s, %s\n", "Hello", "World");
             printf( "%s", buffer);
             return 0;
           }

Again, this is a contrived way of doing the same thing as in [1].


scanf

The natural way of thinking of scanf() is that it reads in formatted input specifed by a format string that is similar to one used by printf(). For example, if we wanted to read in a line that consisted of four integers separated by commas, it might look like this:

[11]       scanf("%d, %d, %d, %d", &num1, &num2, &num3, &num4);

We must immediately point out the presence of the & character that preceeds each of the variables in the argument list. It's not easy to explain why this is necessary until you understand how pointers work. For now, let's assume that the ampersand is necessary only for scanf() arguments that are not strings.

Example [11] provides us with a good case for examining what happens when the input does or does not match the format string. For instance, the following lines:

           1, 2, 3, 4
           10, 34509, 29, 0
           1,2,3,4
               1,  4,   9,   16

all match the format and are successfully read. However, the strings below do not match the format. Examine each line for legality by applying the following criteria: 1) Is the item an integer? 2) Is there a comma immediately following the integer?

           1, 2, 3
           one, two, three, four
           1, two, three, four
           1, 2, three, four
           1, 2, three, four
           1 ,2 ,3 ,4

scanf() will try as hard as it can to match the input to the format string and when it can't, it stops. That means it doesn't necessarily set the value of the variables passed in the arguments. To determine whether scanf()'s input matched the format string, it returns the number of substitution variables it succeeded in matching. For instance, we might change Example [11] to check that the proper number of arguments was matched:

[12]       count = scanf("%d, %d, %d, %d", &num1, &num2, &num3, &num4);
           if (count != 4)
           {
             printf("We only read %d arguments!\n", count);
           }

scanf on strings

Although integers are the primary things we'll want to read, we'll occasionally want to read in other kinds of data. Especially strings. The normal way to read in a string is to use the %sconversion specifier. However, the catch is that, with scanf(), %s only reads the non-white-space characters in the input. So given at statement as in Example [13], we could only read in names that had no spaces in them.

[13]       char name[1000];
           count = scanf("%s", name);
           if (count != 1)
           {
             printf("Problem reading name!\n");
           }

The trick with scanf() is that it adds an additional conversion specifier that can be used to read in a group of input characters that match a specified list. It can also be used to match a group of input characters that DO NOT match a specified list. This new conversion specifier is the bracket specifier [] and the list of legal (or illegal) characters is contained between the brackets. For example, to match a string of any length composed of the characters 'a', 'n' and 'q' in any order, we might try:

[14]       char buffer[1000];
           scanf("%[naq]", buffer);

Naturally, it's a little difficult to specify more than a few characters this way so the bracket conversion specifier also supports ranges using a dash character. The following example reads a string of any length that contains only lowercase letters, digits and the question mark:

[15]       char buffer[1000];
           scanf("%[a-z0-9?]", buffer);

By using the bracket conversion specifier, we could craft a way to read in an arbitrary group of characters. However, what if we wanted to set it up to read in a group of characters that consisted of anything EXCEPT a particular letter? The solution is to use the 'carat' operator inside the bracket specifier. In Example 16, we've set up scanf() to read in a group of 1 or more characters as long as they're anything EXCEPT 'm' or 'Z':

[16]       char buffer[1000];
           scanf("%[^mZ]", buffer);

So, to address our original problem of how to read in strings that contain spaces, we could try the following solutions. First we could try to explicitly define all the legal characters (including the space):

[17]       char buffer[1000];
           scanf("%[ \ta-zA-Z0-9,.:?!@#$%&*()-_+={}]\n", buffer);

Or we could just tell scanf() to read in everything that's not a newline character:

[18]       char buffer[1000];
           scanf("%[^\n]", buffer);

Finally, if we knew that we were reading in a line that contained four strings separated by commas, we might try reading in anything but commas:

[19]       char buffer1[1000];
           char buffer2[1000];
           char buffer3[1000];
           char buffer4[1000];
           scanf("%[^,],%[^,],%[^,],%[^\n]",
             buffer1, buffer2, buffer3, buffer4);

Notice that the last conversion specifier read anything that was not a newline since there would only be three commas separating the four strings.


Friends of scanf

There are two other functions that are similar to scanf() in the same way that some functions are similar to printf(). The functions are fscanf() and sscanf(). Their prototypes are shown below. Determining how they work is an exercise left to the reader but should be fairly straightforward.

           int scanf( char *format, ...);
           int fscanf( FILE *file_pointer, char *format, ...);
           int sscanf( char *buffer, char *format, ...);

One particularly important difference between scanf() and fscanf() and sscanf() is that scanf() naturally assumes that it will see a '\n' at the end of the line. The other functions do not. If you need to read in a newline character with fscanf(), you'll have to put a '\n' in the format string.