Chapter 9 - The printf function
It is a policy of this tutorial that, as you take your first steps in learning the C language, each lesson builds logically on what has gone before. That is, there are (ideally) no forward references in the tutorial. Thus, we couldn't introduce puts until we'd seen how to call a function. Nor could we introduce it until we'd seen how pointers work, and we also had to know about the char type.
Just this one time, though, we're going to break this rule, and introduce some magic. This is because we are getting to the stage where we really, really need to learn about the printf function, and unfortunately this is a function that does rely on magic (i.e. something we're not ready to learn about yet). To minimise the harmful effect of this magic, I'm going to give you a brief peek behind the magician's curtain, and tell you just enough, but as little as possible, about variadic functions.
Variadic functions
So far, every function we have shown you shares an important characteristic: they all take an exact number of parameters. It might be one, it might be two, it might be more... but for any given function, it's always the same number. Because of this, we know how many arguments to provide to the function when we call it. (Arguments are what we pass to a function, and parameters are what the function receives. The clc wiki has a more detailed explanation of the difference between arguments and parameters in case you want to read more about this.)
C does, however, have a mechanism for enabling us to write functions that expect a variable number of parameters. These functions are known as variadic functions. In due course, we will see how to write our own variadic functions. For now, you need only know that they do exist, they're not actually magical, and they have to follow certain rules. Particularly, the writer of a variadic function has to design a scheme whereby he or she can tell how many arguments have been sent to the function, and what types those arguments have.
For now, that's all you need to know about variadic functions: that they exist, and that they aren't psychic -- they have to follow some kind of scheme for interpreting the information they are sent.
First steps with printf
The printf function, then, is a variadic function. It takes at least one parameter, and that parameter must be a string. (Actually, we know that a string is an array of char terminated by a null character, and we know that when we try to pass an array to a function, what actually gets sent is the address of the first element in that array. As long as we remember that this is what's happening, we can afford to be a little relaxed, and talk about "passing a string to a function".
The printf function is prototyped in stdio.h, and on this occasion I'm not going to show you the prototype. We'll cover it when we get onto variadic functions. Instead, let's start to use the function, to get ourselves comfortable with it.
#include <stdio.h> /* pull in the prototype for printf */ int main(void) { printf("Hello, world!\n"); return 0; }
So far, so good. In the above program, printf is behaving rather like puts. We pass it a string argument, and it writes that argument onto the standard output device for us. Unlike puts, however, it will not automatically append a newline character. So if we want one, we have to add one to the string. This is a small price to pay in exchange for the fact that, if we don't want a newline, we don't have to have one.
Printing an int
We have been held back somewhat by the fact that writing an int to the standard output device is rather complicated. We had to write a special function, print_integer, to do it, and that was quite a complicated function (albeit a useful one). But the printf function can easily print an int for us, provided we ask it in the right way.
#include <stdio.h> int main(void) { int i; for(i = 0; i < 10; i++) { printf("%d\n", i); } return 0; }
This program prints the numbers 1 to 10, one number per line. Why?
The first thing to notice is that we are sending not one argument, but two, to printf. The first is, has to be, must be, always will be, a string. The printf function uses this string to learn more about what you want. It reads the string, looking for % signs. When it finds any other character in the string, it just copies that character to the standard output device. But when it finds a % sign, its anthropomorphic ears prick up, and it sets to work. The % sign itself is discarded, and printf looks at what comes next. If it sees a 'd' (known as a conversion specifier), it expects another argument, an int, which it will then print on the standard output device in base ten (decimal, hence the 'd'). Once it's finished doing that, it goes back to reading the string, character by character, as before. In our example, that means it will find the '\n' character next, so it will print a newline character. Then it comes to the null terminator, so it stops and returns.
It is our job to ensure that we pass it the int that it is expecting. If we forget, the compiler doesn't even have to tell us! (It might tell us anyway, but we shouldn't rely on that.) And if we forget, printf won't know that we didn't pass it an int. It will just take some random blob of memory and print that instead, which isn't what we want to happen.
We can, if we like, print more than one int, but we need a separate "%d" for each int, as in the following example:
#include <stdio.h> int main(void) { int miles; int metres; int km; puts("Miles Metres Kilometres"); for(miles = 0; miles < 100; miles += 10) { metres = miles * 1609; km = metres / 1000; printf("%d %d %d\n", miles, metres, km); } return 0; }
The output of this program is:
Miles Metres Kilometres 0 0 0 10 16090 16 20 32180 32 30 48270 48 40 64360 64 50 80450 80 60 96540 96 70 112630 112 80 128720 128 90 144810 144
As you can see, the output is a little ragged. We'll fix that shortly.
Before we do, though, I hope you have already asked yourself an important question. If printf interprets the "%" symbol as introducing a print field that requires a matching parameter, is there any way to print an actual "%" symbol?
The answer is yes, and it's simply this: "%%". If printf sees a "%" sign immediately followed by another one, it ignores the first one, prints the second one on standard output, and no matching parameter is required or desired. Thus:
int pea_lovers = 46; printf("%d%% of respondents like peas.\n", pea_lovers);
will print:
46% of respondents like peas.
Printing a char
We can print individual characters if we like, using the conversion specifier "c", as follows:
#include <stdio.h> int main(void) { int input = getchar(); printf("You typed [%c]\n", input); return 0; }
Note how I used square brackets to mark off the output. You don't have to do this, of course, but it makes the output a little easier to understand when the user types something like a space character or maybe a newline. These square brackets don't mean anything to printf. They are just character data, to be copied to standard output like any other characters.
Printing a string
You can even pass entire strings to printf. To do this, use the conversion specifier s. For example:
#include <stdio.h> int main(void) { char title[] = "Queen"; char name[] = "Victoria"; int accession_year = 1837; printf("%s %s acceded in %d\n", title, name, accession_year); return 0; }
Better format control
We can control the width of a print field by placing the width (in print columns, so it's a whole number) between the % and the conversion specifier. Here is our miles-and-metres program again, but this time the output is a lot neater:
#include <stdio.h> int main(void) { int miles; int metres; int km; puts("Miles Metres Kilometres"); for(miles = 0; miles < 100; miles += 10) { metres = miles * 1609; km = metres / 1000; printf("%5d %6d %5d\n", miles, metres, km); } return 0; }
Here's our reward:
Miles Metres Kilometres 0 0 0 10 16090 16 20 32180 32 30 48270 48 40 64360 64 50 80450 80 60 96540 96 70 112630 112 80 128720 128 90 144810 144
As you can see, the widths we specified have been honoured, and leading blanks have been used to pad out shorter numbers to fill the available space.
Summary
In this chapter, you began to learn about the printf function.
In the next chapter, we will discuss C's unsigned integer types.
Progress
Terminology
- variadic function
- conversion specifier
- precedence
- array
- index
- pointer
- sentinel value
- null character
- null terminator
- string
Syntax
- comments
- types
- char
- operators
- increment and decrement operators
- ++n n++ --n n--
- assignment operators
- = += -= *= /= %=
- additive operators
- the + operator
- the - operator
- multiplicative operators
- the * operator
- the / operator
- the % operator
- equality and relational operators
- == != < > <= >= !
- logical operators
- the && operator
- the || operator
- address and indirection operators
- the unary * operator
- the unary & operator
- the array subscripting operator []
- miscellaneous operators
- the conditional operator ? :
- the comma operator ,
- the sizeof operator
- increment and decrement operators
- control structures
- if/else
- while
- do/while
- for