Introduction to the printf function in the C language
The printf function in C language is one of the most used functions for printing output to the screen. It is part of the standard input/output library (stdio.h) and allows formatting and displaying various data types in a flexible way.
For this reason, it is fundamental to understand from the beginning how to correctly use printf to fully exploit its potential.
- The
printffunction is one of the most common functions in C language for printing formatted output to the screen. - The general syntax of
printfrequires a format string followed by one or more arguments to print. - Format specifiers, which start with the percent symbol (
%), indicate where to insert the values to print and how to format them. - It is important that the number of format specifiers matches the number of arguments provided, otherwise unexpected behaviors could occur.
- Escape sequences, such as
\nfor newline, allow inserting special characters within format strings.
Basic functioning of the printf function
The printf function is designed to display the content of a string, which takes the name of format string, with values that can be positioned within it at some specific points.
A string is a sequence of characters enclosed in double quotes ("); we will return to strings in the next lessons. For now, it suffices to know that a format string can contain normal text and placeholders (or format specifiers) that indicate where to insert the values to print.
When we invoke printf (that is, when we call the function), we must specify at least one argument: the format string. Eventually, we can provide additional arguments that correspond to the placeholders present in the format string.
The general syntax of the printf function is the following:
printf("format string", argument1, argument2, ...);
The arguments following the format string can be constant values, variables or more complex expressions.
In general there is no limit to the number of values that can be printed with a single call to printf, provided that the number of placeholders in the format string corresponds to the number of arguments provided.
As mentioned, the format string can contain both normal text, that is normal characters, and the so-called format specifiers. A format specifier is a sequence of characters that starts with the percent symbol (%) and indicates both the position where to insert a value, and the type of data that one wishes to print.
In particular, the information that follows the percent symbol specifies how a value must be converted from its internal binary representation to a textual representation readable by the user.
For example, the format specifier %d indicates that the value to print is a signed decimal integer, while %f indicates that the value is a floating-point number.
The normal characters present in the format string are printed as they are, without any modification. Conversely, whenever a format specifier is encountered, printf takes the corresponding argument from the list of provided arguments and converts it to a textual representation according to the rules defined by the specifier.
Let's consider an example:
int a, b;
double x, y;
a = 10;
b = 20;
x = 3.14;
y = 2.71;
printf("Integer values: a = %d, b = %d\n", a, b);
printf("Floating-point values: x = %f, y = %f\n", x, y);
In this example, the first call to printf prints the values of variables a and b using the format specifier %d for integers. The second call prints the values of variables x and y using the format specifier %f for floating-point numbers of type double.
The result of executing this code will be:
Integer values: a = 10, b = 20
Floating-point values: x = 3.140000, y = 2.710000
As can be noted, the normal characters in the format string were printed as they are. The format specifiers %d and %f were replaced by the values of the corresponding variables, converted to an appropriate textual representation.
Another observation concerns the fact that, in replacing the format specifiers with the corresponding values, printf follows the order in which the arguments were provided. In our example, the first %d corresponds to the first argument a, the second %d to the second argument b, and so on.
Recapitulating:
printf function
The printf function allows printing formatted text to the screen. Its general syntax is:
printf("format string", argument1, argument2, ...);
The format string can contain normal text and format specifiers, which start with the percent symbol (%) and indicate where to insert the values to print and how to format them.
The arguments following the format string correspond to the placeholders present in the string and are converted to textual representations according to the rules defined by the format specifiers.
In using the printf function, however, one must pay attention to a fundamental aspect: the number of format specifiers present in the string must correspond exactly to the number of arguments provided after the string itself.
Attention to the number of arguments of the printf function
In general, according to the C language standard, it is not necessary for the compiler to check that the number of format specifiers in the string corresponds to the number of arguments provided.
For example, the following code is incorrect:
/* ERROR: insufficient number of arguments */
printf("Values: a = %d, b = %d\n", a);
In this case, the format string contains two format specifiers (%d), but only one argument (a) is provided. However, the compiler might not signal any error but, rather, compile the program without problems. Despite this, the execution of the program could lead to unexpected behaviors, such as printing incorrect values or even crashing the program.
The same applies to the opposite case, in which more arguments are provided than necessary:
/* ERROR: excess number of arguments */
printf("Value: a = %d\n", a, b);
In this case, the format string contains only one format specifier (%d), but two arguments (a and b) are provided. Even in this case, the compiler might not signal any error, but the execution of the program could lead to unexpected behaviors.
In general, it is the programmer's responsibility to ensure that the number of format specifiers corresponds to the number of arguments provided. Otherwise, there is a risk of encountering errors that are difficult to identify and correct.
Another important aspect to consider concerns the type of data associated with each format specifier. Each format specifier is designed to handle a specific data type, and providing an argument of incorrect type can lead to unexpected behaviors.
Attention to the data type of arguments of the printf function
Just as the C language standard does not require the compiler to check the number of arguments provided to printf, it also does not require checking that the data type of each argument corresponds to the type expected by the format specifier.
If the developer provides an argument of incorrect type, the printf function might, in the best case, print meaningless values, or, in the worst case, cause the program to crash.
Let's consider the following example:
int a = 10;
double b = 3.14;
// ERROR: incorrect data types
printf("%f %d \n", a, b);
In this case, the first format specifier %f expects an argument of type double, but an argument of type int (a) is provided. Similarly, the second format specifier %d expects an argument of type int, but an argument of type double (b) is provided.
Since the printf function follows the specifications provided in the format string pedantically, it will print a double followed by an int, but the printed values will make no sense, since the data types do not correspond.
To avoid these problems, it is fundamental that the developer ensures that the data type of each argument corresponds to the type expected by the corresponding format specifier.
Introduction to Format Specifiers
Format specifiers provide a great capacity for control over the output generated by the printf function. But, often, they can appear cryptic and complex to read, especially for novice developers.
Indeed, at this point in the guide it is too premature to provide all the details about them and their advanced functionalities. We postpone this topic to subsequent lessons, where they will be treated in a more in-depth manner.
For the moment, we will concentrate on their main aspects.
In general, the most common and simple form of a format specifier is the following:
%-m.pX
This syntax is composed of some mandatory parts:
- The percent symbol (
%), which indicates the beginning of a format specifier. This symbol is mandatory and must be present in every format specifier. - A combination of letters,
X, also mandatory, which indicates the type of data to print. For example,dfor decimal integers,ffor floating-point numbers of typedouble,cfor characters, and so on.
And some optional parts:
- A minus sign (
-), which indicates left alignment of the printed value. If it is not present, the value is aligned to the right by default. - A positive integer number,
m, which specifies the minimum width of the field in which the value will be printed. If the value is shorter than this width, it will be filled with empty spaces. - A point (
.) followed by a positive integer number,p, which specifies the precision of the printed value. For floating-point numbers, this indicates the number of digits to print after the decimal point. Ifpis not specified the point must also be omitted.
Minimum field width
The minimum field width (m) specifies the minimum number of characters that must be used to print the value. If the value to print is shorter than this width, the value will be printed aligning it to the right and empty spaces will be added to the left to reach the minimum width.
For example, suppose we want to print the integer number 123 with a minimum field width of 5:
int num = 123;
printf("Number: %5d\n", num);
The output will be:
Number: 123
In this case, the number 123 is printed with two empty spaces to the left to reach the minimum width of 5 characters.
Conversely, if the value to print is longer than the specified minimum width, the value will be printed in its entirety without any modification.
For example, if we wanted to print the integer number 12345 with a minimum field width of 3:
int num = 12345;
printf("Number: %3d\n", num);
The output will be:
Number: 12345
Therefore, in this case, the minimum field width was ignored because the value to print is longer than 3 characters. In this way, printf guarantees that the value is always printed in its entirety, regardless of the specified minimum width.
To the minimum field width one can also associate the minus sign (-), which indicates left alignment of the printed value. For example, if we wanted to print two integer numbers with a minimum field width of 5, but aligned to the left:
int num1 = 123;
int num2 = 456;
printf("Numbers: %-5d %-5d\n", num1, num2);
The output will be:
Numbers: 123 456
Precision
Format specifiers can also include an optional part that indicates the precision (.p).
The meaning of the precision value depends on the type of data being printed. In the case of floating-point numbers, the precision indicates the number of digits to print after the decimal point.
For example, suppose we want to print the floating-point number 3.14159 with a precision of 2 decimal digits:
double num = 3.14159;
printf("Number: %.2f\n", num);
The output will be:
Number: 3.14
In this case, the number 3.14159 is rounded to 3.14, since we specified a precision of 2 decimal digits.
Note that in the case of floating-point numbers, if the precision is not specified, printf uses a default precision of 6 decimal digits.
Moreover, the value is rounded and not truncated. For example, if we wanted to print the number 2.6789 with a precision of 3 decimal digits:
double num = 2.6789;
printf("Number: %.3f\n", num);
The output will be:
Number: 2.679
In this case, the number 2.6789 is rounded to 2.679, since the fourth decimal digit (9) is greater than or equal to 5.
The Most Common Format Specifiers
As we have seen, the most common form of a format specifier is %-m.pX, where X indicates the type of data to print.
Let's see what the most common format specifiers are and their meanings:
-
%d: Prints a signed decimal integer (typeint).When using this specifier, the precision (
.p) indicates the minimum number of digits to print. If the number has fewer digits, zeros will be added to the left.In case the precision is omitted, it is assumed by default equal to
1. Therefore, the following specifiers are equivalent:%dand%.1d. -
%e: Prints a floating-point number in scientific notation (typedouble).The precision (
.p) indicates the number of digits to print after the decimal point. In case the precision is omitted, it is assumed by default equal to6. Conversely, if the precision is0, no decimal point will be printed.For example, wanting to print the number
12345.6789with a precision of2decimal digits in scientific notation:double num = 12345.6789; printf("Number: %.2e\n", num);The output will be:
Number: 1.23e+04 -
%f: Prints a floating-point number in decimal notation (typedouble).The precision (
.p) indicates the number of digits to print after the decimal point. In case the precision is omitted, it is assumed by default equal to6. Conversely, if the precision is0, no decimal point will be printed.For example, wanting to print the number
3.14159with a precision of3decimal digits:double num = 3.14159; printf("Number: %.3f\n", num);The output will be:
Number: 3.142 -
%g: Prints a floating-point number using the most compact notation between decimal and scientific (typedouble).With this specifier, the precision (
.p) no longer indicates the number of decimal digits, but the total number of significant digits to print.Moreover, unlike the
%fspecifier, this specifier does not add zeros at the end of the number to reach the specified precision.In addition, if the number has no decimal digits, no decimal point will be printed.
This specifier is particularly useful when one wants to print numbers whose size is not known in advance or when the latter varies considerably. When using it to print numbers that are not very large or not very small, one obtains output in decimal format, as if we had used
%f. Conversely, when printing very large or very small numbers, one obtains output in scientific notation, as if we had used%e, thus saving precious space.
In addition to these format specifiers, there exist many others that allow printing different types of data, such as characters, strings, hexadecimal numbers, octal numbers, and so on. These will be treated in a more in-depth manner in subsequent lessons as we address more advanced topics related to the printf function.
Complete Example
Let's now try to put together what we have learned so far with an example that prints both integer and floating-point numbers, using various format specifiers.
#include <stdio.h>
int main() {
int a = 42;
double b = 1024.56789;
printf("|%d|%5d|%-5d|%5.3d|\n", a, a, a, a);
printf("|%10.3f|%10.3e|%-10.3g|\n", b, b, b);
return 0;
}
If we try to compile and execute this program, we will obtain the following output:
|42| 42|42 | 042|
| 1024.568| 1.025e+03|1.02e+03 |
In this example, we defined two variables: a of type int and b of type double. Subsequently, we used the printf function to print the values of these variables with different format specifiers.
We used the character | to visually delimit the printed fields, in order to highlight the effect of the minimum field width and alignment.
Now, let's see in detail the individual format specifiers used:
%d: Prints the value ofaas a signed decimal integer using the minimum possible space.%5d: Prints the value ofawith a minimum field width of5, aligned to the right. Sinceahas only2digits,3empty spaces will be added to the left.%-5d: Prints the value ofawith a minimum field width of5, aligned to the left. Sinceahas only2digits,3empty spaces will be added to the right.%5.3d: Prints the value ofawith a minimum field width of5and a precision of3digits. Sinceahas only2digits, one zero will be added to the left to reach the precision of3, and2empty spaces will be added to the left to reach the minimum width of5.%10.3f: Prints the value ofbas a floating-point number with a minimum field width of10and a precision of3decimal digits. Sincebhas7digits before the decimal point and5decimal digits,2empty spaces will be added to the left to reach the minimum width of10.%10.3e: Prints the value ofbin scientific notation with a minimum field width of10and a precision of3decimal digits. Also in this case,2empty spaces will be added to the left to reach the minimum width of10.%-10.3g: Prints the value ofbusing the most compact notation between decimal and scientific, with a minimum field width of10and a precision of3significant digits, aligned to the left. Sincebis printed in scientific notation with4significant digits,6empty spaces will be added to the right to reach the minimum width of10.
In this way, the example demonstrates how to use the printf function with various format specifiers to control the appearance of the output printed to the screen.
Introduction to Escape Sequences
We conclude this introduction to the printf function by talking about escape sequences.
Often, in the programs seen so far, we inserted, at the end of format strings, the escape sequence \n. This special sequence indicates to printf to go to a new line after printing the content of the string.
This sequence is part of a broader set of strings that collectively take the name of escape sequences. They are used to represent special characters within strings, which otherwise could not be inserted directly from the keyboard or would have a different meaning for the compiler, such as for example double quotes (").
There exist numerous escape sequences, which we will see in detail later, but for now it suffices to know the most common ones:
\n\t\a\b
When the printf function encounters an escape sequence within the format string, it interprets the sequence as an action to perform, rather than as a simple series of characters to print.
Therefore, when it encounters the sequence \n, printf goes to a new line, moving the cursor to the next line. Similarly, the sequence \t inserts a horizontal tab, moving the cursor to the next tab position. The sequence \a produces an acoustic signal (a "beep") on some terminals, while the sequence \b moves the cursor back one character, erasing the previous character.
The name escape derives from the fact that these sequences allow "escaping" from the normal behavior of character printing, allowing the insertion of special characters or controlling the format of the output in ways that otherwise would not be possible.
A format string passed to the printf function can contain multiple escape sequences, and these can be combined with normal text and format specifiers to create complex and well-formatted output.
For example, let's consider the following code:
printf("Hello,\nWorld!\n");
printf("Column1\tColumn2\tColumn3\n");
printf("Warning!\a\n");
The output of this code will be:
Hello,
World!
Column1 Column2 Column3
Warning!
In this example, the first call to printf uses the escape sequence \n to go to a new line after "Hello," and after "World!". The second call uses the escape sequence \t to insert tabs between the columns. The third call uses the escape sequence \a to produce an acoustic signal before printing "Warning!".
There also exist some escape sequences that allow printing special characters within strings, such as for example:
\\: Prints a backslash (\).\": Prints a double quote (").
Indeed, since the backslash (\) is the escape character, to print a backslash itself it is necessary to use the sequence \\. Similarly, to print a double quote within a string enclosed in double quotes, it is necessary to use the sequence \".
For example, let's consider the following code:
printf("File path: C:\\Documents\\File.txt\n");
printf("Quote: \"Knowledge is power.\"\n");
The output of this code will be:
File path: C:\Documents\File.txt
Quote: "Knowledge is power."
In this example, the first call to printf uses the escape sequence \\ to print the backslashes in the file path on a Windows system. The second call uses the escape sequence \" to print the double quotes around the quote.