Learning Objectives
- Be able to use printf, scanf, and sscanf.
- Understand formatting specifiers.
- Understand modifiers to formatting specifiers.
- Be able to left or right justify a formatting field.
- Be able to specify the minimum number of characters for a field.
General I/O
The C-style functions, printf, scanf, and sscanf come from the #include <cstdio> header (in C++). All three of these functions use a formatting string, which tells the function how to print or read values.
Formatting Strings
A formatting string is a C-style string. Formatting specifiers start with a percent sign (%). After this percent sign, you can include modifiers and then it ends with a specifier. The following table shows some specifiers.
| Specifier | Output |
|---|---|
| %d | A signed, 32-bit integer in base 10. |
| %ld | A signed, 64-bit integer in base 10. |
| %u | An unsigned, 32-bit integer in base 10. |
| %lu | An unsigned, 64-bit integer in base 10. |
| %x | A 32-bit integer in base 16. |
| %lx | A 64-bit integer in base 16. |
| %f | A 32-bit float. |
| %lf | A 64-bit float. |
| %p | A pointer. |
| %s | A C-style string. |
| %c | A single character (be careful, this includes whitespace!) |
We can add specifiers and other text as well. This makes these very easy to use.
#include <cstdio>
int main() {
int i = 100;
long j = 200;
unsigned int p = 44;
char *v = new char;
printf("Integer: %d\nLong: %ld\nUint: %u\nPointer: %p\n", i, j, p, v);
return 0;
}
The code above prints the following on my machine. Since the pointer’s value will be dynamic, YMMV.
Integer: 100 Long: 200 Uint: 44 Pointer: 0x55c5373c6eb0
Notice that printf substitutes all specifiers with the value. These are in order after the formatting string. The integer comes first, then the long, then the unsigned integer, then the pointer.
We can do the same thing with “scanning”. We generally don’t put anything but specifiers in the formatting string for scanning. scanf() and sscanf() are almost identical, except scanf() will read from console input, whereas sscanf stands for “string” scanf, and instead of reading from the console, it reads from a string, much like a string stream.
The scanf and sscanf functions return the number of formatters that were able to be scanned. I will show a C++ example using cout and cin, and then I will show how we use printf and scanf to achieve the same results.
#include <iostream>
using namespace std;
int main() {
char name[50];
int age;
cout << "Enter your name: ";
cin >> name;
cout << "Enter your age: ";
cin >> age;
cout << "You are " << name << ", and your age is " << age << " years.\n";
return 0;
}
In C++, we can use printf and scanf by including cstdio. We can put using namespace std, but it isn’t necessary.
#include <cstdio>
int main() {
char name[50];
int age;
printf("Enter your name: ");
scanf("%49s", name);
printf("Enter your age: ");
scanf("%d", &age);
printf("You are %s, and your age is %d years.\n", name, age);
return 0;
}
Notice that I put %49s. This is the specifier %s with the modifier 49. I will cover modifiers below, but this only scans up to 49 characters. We can’t specify 50 because we need the null-terminator, which is 0 to be tacked on the end.
Modifiers
Modifiers modify the behavior of our specifiers. Modifiers act much like the I/O manipulators you used in C++, but they have a different format. We can specify the precision of a floating point number. We can specify left justification and right justification as well as the field width. The syntax is quite a bit different, but hang in there!
Field Widths
We can specify a left-justified or right-justified field and a given width using modifiers. We put a number in front of the value we want to print for a right-justified field, or a negative number in front of the value we want to print in a left-justified field.
int i = 100;
printf("| %10d | %-10d |\n", i, i);
The code above prints the following:
| 100 | 100 |
As you can see, the positive %10d started printing the value 100 from right-to-left, whereas the negative field specifier made it a left-justified field.
Unlike I/O manipulators, modifiers must be specified for every field we want. Recall that some I/O manipulators are persistent. This concept does not apply to C-style formatters.
Number Modifiers
Sometimes we want to modify a number. We usually use number modifiers for floating point values, but we can use them for integer values too. If we want to specify the precision we add .precision followed by f or lf.
float j = 100.2572;
printf("%.2f\n", j);
The code above prints 100.26. Notice that when using the modifier, it rounds the value, just like the I/O manipulator setprecision does. Using the %lf and %f, the precision refers to the number of digits to the right of the decimal point.
We can combine precision and a field by separating the field width by the .:
float j = 10.7455;
printf("| %10.2f | %-10.2f |\n", j, j);
The code above prints the following:
| 10.75 | 10.75 |
Notice that 10.75 takes up 5 characters, so yes, the decimal point itself takes up one character of the field width.
One interesting modifier is for a string. You saw above where we can use %49s in scanf to prevent scanning more characters than we have storage space available. However, we can do something like this for output too. However, %49s for output means that we want a right-justified field of 49 characters. So, instead, we have to use the precision modifier for a string.
#include <cstdio>
int main() {
char value[] = "Hello World!";
printf("%.5s\n", value);
return 0;
}
The code above only prints the first 5 characters of the character array, value. So, we get the output:
Hello
The printf function will print as many characters you want unless it first reaches the end of the string (the NULL terminator).
Scanning and Printing Strings
The printf() and scanf() functions read from and write to the console, respectively. However, there are C-style string streams too. Unfortunately, the C-style string streams operate on character arrays (C-style strings), so we have to be careful about our storage space.
An ostringstream (output string stream) for C uses the sprintf (string printf) function, whereas the istringstream (input string stream) for C uses the sscanf (string scanf) function.
#include <cstdio>
int main() {
char input[] = "123.4 567 91011";
float val1;
int val2;
long val3;
sscanf(input, "%f %d %ld", &val1, &val2, &val3);
char output[125];
sprintf(output, "You gave me %.3f for the float.\n", val1);
printf("%.25s, and you gave me %d and %ld for the integers.\n", output, val2, val3);
return 0;
}
The code above produces the following output:
You gave me 123.400 for t, and you gave me 567 and 91011 for the integers.
Notice that the first string is truncated. That’s because I used a modifier of .25s to only print the first 25 characters of the string. This shows you that we’re actually printing the string produces with sprintf, and I’m not tricking you in any way.
You can also see that sscanf will read values from a string. Recall that in C++ using string streams, we sometimes read an entire line and then broke apart that line using a string stream. We can do the same in C, except we use fgets to “get a string”.
#include <cstdio>
int main() {
char input[128];
int value;
char command[2];
do {
printf("Enter command, type 'q' to quit: ");
fgets(input, 128, stdin);
sscanf(input, "%1s", command);
if (command[0] == 'q')
break;
} while (true);
return 0;
}
So, why did I use %s instead of %c above? Well, we have a little bit of an issue. %c will read ANY character, including whitespace, whereas %s will skip whitespace. So, if the user enters \nq\n, then we will get \n returned back to us. That won’t do us any good. So, we use a string instead, and then only look at the first character. Recall that we need at least two bytes: one for the character and one for the null terminator.
Console File Streams
When a program is executed, the operating system automatically creates three console streams to read from and write to the console–that is, unless we change this behavior. We get three console file streams: stdin (standard input), stdout (standard output), and stderr (standard error).
cout is the same as writing to stdout, cerr is the same as writing to stderr, and cin is the same as reading from stdin. Recall that the difference between cout and cerr is that cerr will immediately output what you give it, whereas cout doesn’t make such guarantees.
What’s neat about C-style console streams is that the operating system and C treat them like a file. That’s why I used fgets (file get string). This function will read up to the maximum size we specify from the given stream (stdin in our case). However, if fgets() gets a newline, it’ll also terminate. This acts much like the getline function in C++.
Checking Return Values
I’ve been pretty bad in this entire tutorial about not checking return values. The fact is, you won’t see many people checking the return value of printf. We just really don’t care that much. However, for scanf and sscanf, this is very important because scanf and sscanf can fail if they try to read something that doesn’t fit your specifiers. Recall that we check sin by putting it in an if statement. We don’t do this for scanf or sscanf, but recall that these functions will return the number of fields it was able to extract. Let’s look at an example:
#include <cstdio>
int main() {
int a, b, c;
printf("Please enter three integers: ");
if (3 != scanf("%d %d %d", &a, &b, &c)) {
printf("I couldn't read all three values :(\n");
}
else {
printf("I read %d, %d, and %d\n", a, b, c);
}
return 0;
}
Let’s see how our program works:
Please enter three integers: john 10 20 I couldn't read all three values :(
Please enter three integers: 200 500 600 I read 200, 500, and 600
Notice that when I specified three integers, scanf returned the value 3, which passed the if check. However, when I put john for an integer, scanf couldn’t figure out how to scan that using the %d specifier, so it couldn’t read all three specifiers, and hence it failed the if check and gave us an error.
Conclusion
This was a quick reference to get you started using printf, sprintf, scanf, and sscanf. Here are some resources you can use:
SCANF function: http://www.cplusplus.com/reference/cstdio/scanf/
SSCANF function: http://www.cplusplus.com/reference/cstdio/sscanf
PRINTF function: http://www.cplusplus.com/reference/cstdio/printf/
SPRINTF function: http://www.cplusplus.com/reference/cstdio/sprintf