PEG:C++ Lesson 6 for Pascal Users

From PEGWiki
Revision as of 00:04, 7 February 2010 by Jargon (Talk | contribs) (C++ Lesson 6 (More I/O) from the email (subject "RE: C++ Lesson 5"))

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
← Previous Lesson | Next Lesson →

C++ programmers find themselves in the wonderful position of having three parallel sets of I/O functions available to them.

The first level is the operating-system level. In Linux, there are functions, mostly located in unistd.h, that make direct requests to the kernel of the operating system to perform the desired I/O operations. There is little point to using these, however, because they are inconvenient (e.g., you cannot read and write integers, only bytes) and depend on a particular operating system. (They do not, for example, work on Windows; Windows has its own set of such functions, which in turn do not work on the judge.) They include open(), read(), and write(). We will not discuss them any further here, and I strongly discourage their use in algorithm problems, unless you are masochistic enough to submit assembly language solutions, in which case you have no choice.

As you probably know, almost everything that can be done in C can be done in C++. The developers of the C standard library designed its I/O functions to be much less painful for the programmer as well as portable from machine to machine. This is why you can code on a Windows machine and submit to a Linux judge and get the same results. From them we have a second set of I/O functions that includes fopen(), scanf(), printf(), gets(), puts(), getchar(), putchar(), fread(), and fwrite(), defined in the header stdio.h.

Of these, you should pay close attention to scanf() and printf(). They will do almost everything that cin >> and cout << do, respectively, but often more quickly and elegantly. They work by parsing a format string to determine the expected format.

For example, the statement printf("%d",x); assumes that x is an integer variable, and prints its value. The code %d stands for int. You are not limited to printing one item at a time; printf("Here are two integers: %d and %d\n",x,y);, when executed with the values x=3 and y=5, will print "Here are two integers: 3 and 5", followed by a newline (that's what '\n' stands for). The same effect can be achieved by the code cout << "Here are two integers: " << x << " and " << y << endl;, but the printf() code is far more elegant and less cluttered. In a similar vein, %f prints a float or double, and %c prints a char. You can have as many of these %-expressions (format specifiers) in a format string as you wish. What is very useful about printf() is that it makes it easy to print fractional numbers to the specified number of decimal places: the command printf("%.2f",3.14159);, for example, prints "3.14". Remember that the order of parameters after the format string must match the order of format specifiers, from left to right, in the format string, if printf() is to work as intended. (To print a literal percent sign, use %%.)

scanf() is very similar. It attempts to match the format string with the actual data available from input. When it encounters a format specifier, it attempts to read the type of value specified and store it into the corresponding variable. There is a bit of a snag here. In Pascal, you were able to pass parameters either by value or by reference, but the distinction was transparent: all that changed was adding the keyword var. C did not work this way; to pass by reference, you had to pass the address of the variable as a pointer by prefixing it with an ampersand, &. For example, the command scanf("Here are two numbers: %d and %f",&x,&y); will attempt to read the string "Here are two numbers: ", followed by an int (storing it into x), the string " and ", and a float (storing it into y). If a matching error occurs (e.g., if we reach the end of file and there is no more data, or if the input does not contain the strings we expected, or if strange characters are present), scanf() returns -1. (Note that whereas %f prints both floats and doubles, scanf() distinguishes between them; %f reads floats whereas %lf reads doubles.)

More information, including additional format specifiers (yes, there is one for every basic type in C --- but note that bool did not exist in C and was introduced in C++) and cool features can be found on the appropriate pages of [1].

The third set of I/O functions is the set that involves cin and cout, defined in the iostream header. (iostream includes stdio.h automatically.) These were introduced with C++. The insertion and extraction operators usually suffice, but there are additional functions such as getline(), cin.getline(), and cin.ignore() that you may want to read up on. Controlling the format of output cannot be done as elegantly as it can with printf(); instead, the header iomanip provides a solution to this task. I will assume that you are all competent users of search engines and leave it to you to learn how to use iomanip, should the need arise.

I took the time to explain scanf() and printf() not because of their elegance, though. Rather, it is because in g++ versions before 4.1 (I think), cin >> was very slow, and cout << somewhat slow. So slow, in fact, that it is actually possible, on some problems, to TLE just from reading input. (An example is Ship & Shop.) When working a problem from somewhere other than the PEG Judge that has large cases (i.e., more than 50 kB input or 500 kB output), I strongly recommend that you play it safe by using scanf() and printf() instead of cin >> and cout <<.

What about on the Judge? In recent g++ versions, including the 4.1 used by the Judge and beyond, the design flaws of cin >> and cout << have been largely fixed. (I do not know if they persist in Visual C++, but then again online judges don't use that anyway.) However, they are still slow because they have to constantly synchronize with the I/O functions provided by C. You can circumvent this by promising the program that you won't use any C input functions, and using the statement cin.sync_with_stdio(false);; similarly, if you won't use any C output functions, you can use cout.sync_with_stdio(false);. These increase the speed of cin >> and cout << (respectively) dramatically on the recent g++ versions, so that they should be fast enough to solve any problem; in fact, they are often faster than scanf() and printf(). (If you break your promise, though, you will get some nasty error like SIGSEGV, or your program just won't work properly and you'll get WA.)

Notice that the sync_with_stdio(false) trick doesn't work particularly well on judges with older g++ versions, so, as I said, only use it on the PEG Judge, unless you are absolutely sure that the judge to which you submit your code has one of the newer g++ versions. On older versions, scanf() and printf() are often your only reasonable choices.