CS13002 Programming and Data Structures

Spring semester

Input/Output

This is yet another imperative feature of C. Reading values from the user and printing values to the terminal impart a sequential flavor to the program. If you print a variable and then do some computation, you get some output. Instead, if you do the computations first and then print the same variable, you may get a different output. It is very essential that you understand the precise flow of execution of a C program. Well, so far you have encountered only flat sequences of statements executed one-by-one from top to bottom. Things start getting complicated as you encounter jump instructions (conditionals, loops and function calls). For effective computation you need these jump instructions. Imperative programming may be a complete mess, unless you understand the control flow thoroughly.

Standard input/output

This is the direct method of communicating with the user, namely, reading from and writing to the terminal. Here are the basic primitives for doing these.

scanf
Read from the terminal.

printf
Write to the terminal.

Scanning values

The usage procedure for scanf is as follows:

   scanf(control string, &var1, &var2, ...);

The primitive scanf waits for the user to enter a value by the keyboard. After the user writes a value and hits the enter button, the value goes to the memory location allocated to the variable specified. So scanf is another way of assigning values to variables.

The control string specifies the data type that is to be read from the terminal. Here is a list of the most used formats:

%dRead an integer in decimal.
%oRead an integer in octal.
%x,%XRead an integer in hexadecimal.
%i

Read an integer in decimal/octal/hex. If the integer starts with 0x or 0X, treat it as a hexadecimal integer, else if it starts with 0, treat it as an octal integer, otherwise treat it as a decimal integer.

%uRead an unsigned integer in decimal.
%hd,%hi,%ho,%hu,%hx,%hXRead a short integer.
%ld,%li,%lo,%lu,%lx,%lXRead a long integer.
%Ld,%Li,%Lo,%Lu,%Lx,%LX

Read a long long integer. This is not an ANSI C feature, but works well in Linux. Replacing L by ll (i.e., using %lld, %lli, etc.) continues to work in Linux and may be better ported to other architectures. Some compilers also support %q (quad).

%fRead a float.
%eRead a float in the scientific (exponential) notation.
%lf,%leRead a double.
%Lf,%LeRead a long double.
%cRead a single character.
%sRead a string of characters.

Example

   int a;
   unsigned long b;
   float x, y;
   char c, s[64];

   scanf("%d",&a);   /* Read the integer a in decimal */
   scanf("%x",&b);   /* Read the integer b in hexadecimal */
   scanf("%f",&x);   /* Read the floating point number x in decimal notation */
   scanf("%e",&y);   /* Read the floating point number y in the scientific notation */
   scanf(" %c",&c);  /* Read the character c */
   scanf(" %s",s);   /* Read a string and store in s */
                     /* For reading strings the ampersand (&) is not needed */

Suppose that the user enters the following values:

   123 123 -123.456 1.23e-6 a Hey! I am your instructor.

Most of the readings go as expected. a receives the decimal value 123, b receives 0x123 (which is 291 in decimal), x and y respectively receive -123.456 = -1.23456e2 and 1.23e-6 = 0.00000123. Also c obtains the value 'a' (whose ASCII value is 97).

However, a problem comes with the string s: it receives the value "Hey!" only. The rest of the input is lost! The situation is actually worse: the rest is not lost. The computer remembers this part and supplies this to the next scanf, if any is executed. Why does it occur? The reason is: scanf stops reading as soon as it encounters a white character (space, tab, new line, etc.). You have to do something more complicated in order to read strings with spaces. Note also that the scanning of c requires a space before the %c. This is for consuming the space following the value of y given by the user. The same applies to the reading of s. Reading characters and strings is often too painful in C. Here are the basic rules:

You can read more than one variables in a single scanf. The six scanf's for the last example can be combined as:

   scanf("%d %x %f %e %c %s", &a, &b, &x, &y, &c, s);
Spaces are ignored before numbers. So the statement
   scanf("%d%x%f%e %c %s", &a, &b, &x, &y, &c, s);

has the same effect. You may use other separators instead of space. For example, the following statement

   scanf("%d,%x,%f,%e,%c,%s", &a, &b, &x, &y, &c, s);

requires you to enter the input values as:

   123,123,-123.456,1.23e-6,a,Hey! I am your instructor.

In all these examples, the string s is assigned the value "Hey!". Use the fgets primitive (see below) to repair this.

It is also a queer thing to use & in every argument except strings. This is because scanf is a function. In C, every function call is of the type call-by-value. In order to see the desired effects (assignments of the arguments by the values given by the user), we need to pass addresses of the variables. A string (character array) is, however, already an address (a pointer), so we don't require an extra &. All these concepts will gradually be clear, as you understand more and more of the idiosyncracies of C. For the time being just rehearse and memorize the following two lines:

   You need ampersands for all things,
   Unless you are scanning strings.

Printing values

Printing is remarkably neater than scanning. No ampersands. And printf prints precisely what you ask it to do. The basic syntax is very similar to scanf.

   printf(control string, arg1, arg2, ...);

This directive causes the program to print the values of the arguments arg1, arg2, ... to the terminal following the format specified in the control string. The control string may contain (almost) any sequence of characters with special escape sequences (starting with percents) that determine how to print the arguments. The argumnets, on the other hand, specify what to print. Here is a list of the basic escape sequences:

%d,%iPrint an integer in decimal.
%oPrint an integer in octal.
%xPrint an integer in hexadecimal. Use the digits 0,1,...,9,a,b,c,d,e,f.
%XSame as %x except that the digits 0,1,...,9,A,B,C,D,E,F are used.
%uPrint an unsigned integer in decimal.
%hd,%hi,%ho,%hu,%hx,%hXPrint a short integer.
%ld,%li,%lo,%lu,%lx,%lXPrint a long integer.
%Ld,%Li,%Lo,%Lu,%Lx,%LX

Print a long long integer. (Not in ANSI C. ll may be used in place of L. Some compilers support %q.)

%fPrint a float in decimal.
%ePrint a float in the scientific (exponential) notation.
%ESame as %e except that E is used to denote the exponent.
%a

Print a float in hexadecimal. Digits 0,1,...,9,a,b,c,d,e,f are used and the exponent indicator is p.

%ASame as %a except that A,B,C,D,E,F and P are used.
%lf,%le,%lE,%la,%lAPrint a double.
%Lf,%Le,%LE,%La,%LAPrint a long double.
%cPrint a single character.
%sPrint a string of characters.
%%Print a literal %.
\"Print a literal double quote ".

Example: Suppose you want to print the scanned values from the notorious scanf example.

   int a;
   unsigned long b;
   float x, y;
   char c, s[64];

   scanf("%d %x %f %e %c %s", &a, &b, &x, &y, &c, s);

   printf("a = %d = 0x%x\n", a, a);
   printf("b = %d = 0x%x\n", b, b);
   printf("x = %f = %e\n", x, x);
   printf("y = %f = %e\n", y, y);
   printf("c = '%c' = %d\n", c, c);
   printf("s = %s\n", s);

If you supply the inputs

   123 123 -123.456 1.23e-6 a Hey! I am your instructor.

the printf statements print the following lines:

   a = 123 = 0x7b
   b = 291 = 0x123
   x = -123.456001 = -1.234560e+02
   y = 0.000001 = 1.230000e-06
   c = 'a' = 97
   s = Hey!

Once again you may combine several printf's in a single statement. For example, the same output is produced by the following:

   printf("a = %d = 0x%x\nb = %d = 0x%x\n", a, a, b, b);
   printf("x = %f = %e\ny = %f = %e\n", x, x, y, y);
   printf("c = '%c' = %d\ns = %s\n", c, c, s);

Here look at the dual meaning of characters. When viewed as a character, it looks like a; when viewed as an integer, it looks like 97.

During printf no values are assigned. So printf can legally handle printing values of expressions. Thus an argument of printf can be any valid expression. For example, the following snippet

   int a = -3, b = 5;

   printf("expression1 = %d, and ", a / (a + b));
   printf("expression2 = %f.\n", (float)a / (float)(a + b));
   printf("That's all!\n");
prints
   expression1 = -1, and expression2 = -1.500000.
   That's all!

There is a funny thing about printf. It indeed returns a value, namely, the number of characters printed. Here is an example:

   int a = -3, b = 5;
   int n;

   n = printf("expression1 = %d, and ", a / (a + b));
   n += printf("expression2 = %f.\n", (float)a / (float)(a + b));
   n += printf("That's all!\n");
   printf("Total number of characters printed before this line = %d\n", n);

The output is

   expression1 = -1, and expression2 = -1.500000.
   That's all!
   Total number of characters printed before this line = 59

How come? You can see only 57 printed characters. Yep! You forgot to count the new-line characters at the end of the first two lines.

File input/output

So far you have seen examples of I/O from/to the terminal. This is a special case of what is called file I/O. You can read from or write to any file using built-in functions that have call syntaxes very similar to the standard I/O calls.

In order to use a file you must first open a file pointer or a file descriptor. The fopen call can be used for that. Here are the three basic ways of opening a file descriptor.

   FILE *ifp, *ofp1, *ofp2;       /* Declare FILE pointers */

   ifp = fopen("foo.in","r");     /* Open the file "foo.in" in read mode */
   ofp1 = fopen("bar1.out","w");  /* Open the file "bar1.out" in write mode */
   ofp2 = fopen("bar2.out","a");  /* Open the file "bar2.out" in append mode */

Once the file pointers are opened, they can be used for reading from or writing to the named files. For the last example, the file "foo.in" is opened in the "read" mode, i.e., you can read from the file "foo.in". The file "bar1.out", on the other hand, is opened in the "write" mode. The file, if existent, is rewritten, else a new file in the name "bar1.out" is opened. You can write whatever you like to this file. Finally, the file "bar2.out" is opened in the append mode. You can write to the file "bar2.out". However, writing starts at the end. This means that if a file with the name "bar2.out" already exists, then its content is left unaltered, but now you get the facility to write to this file starting from the end of the file. If "bar2.out" didn't exist, one new file is created with this name and you can now start writing to it.

Reading from and writing to a file can be effected only via the FILE pointers opened. The fopen call simply associates a file name and an access mode with a FILE pointer.

If ifp is a FILE pointer opened in the read mode, you can read from it using the directive:

   fscanf(ifp, control string, &var1, &var2, ...);

Here control string and the arguments are to be used exactly in the same way as explained in connection with scanf.

Similarly, if ofp is a FILE pointer opened in the write or append mode, one can use the following call for writing to the file:

   fprintf(ofp, control string, expr1, expr2, ...);

Like printf, the control string specifies how to print and the arguments expr1, expr2, ... indicate what to print.

When your program starts execution, three FILE pointers are opened by default. The standard input stdin is opened in the read mode for scanning values from the terminal. The standard output stdout and standard error stderr descriptors are opened in the append mode. Both are meant for writing to the terminal. With special shell commands one can separate out the two output streams. In Unix-like platforms almost everything under the sun is treated as a file. Hard disk files look like files, but the terminal is also a file and can be read from and written to. In fact, the call

   scanf(control string, &var1, &var2, ...);

is equivalent to the call

   fscanf(stdin, control string, &var1, &var2, ...);

Similarly, the call

   printf(control string, expr1, expr2, ...);

is equivalent to the call

   fprintf(stdout, control string, expr1, expr2, ...);

There are a lot of other things that you can do using FILE pointers. We won't go into the details here. We only mention a new call to do something useful: reading a string with spaces. The call goes like this:

   fgets(str, n, ifp);

Here str is a character array, n a positive integer, and ifp a FILE pointer opened in the read mode. The call reads an entire line from the FILE pointer ifp and stores the line with a trailing NULL character ('\0') in the string str. If the line in the input file is bigger than n characters, then only n-1 characters are read and stored in str together with the trailing NULL character. The array str should be large enough to accommodate n characters. Using a smaller array may corrupt memory and/or raise segmenation faults.

But what about reading an entire line from the terminal, as our original problem was? You still wonder how! That's damn easy:

   fgets(str, n, stdin);
Period! Nay, semi-colon;

Once you are through working with a FILE pointer fp and do no longer require it, you may explicitly close the pointer using the call:

   fclose(fp);

When your program terminates, all opened pointers (including the standard ones) are closed. Doing it explicitly is a matter of good programming etiquette and is on esoteric situations needed for your survival. Every system imposes a restriction on the maximum number of FILE pointers that can be opened simultaneously. This upper bound is compiler-dependent and is usually not very high. If this value is 16, and you need to access 25 files, and if we assume you do not need to access all these 25 files simultaneously, it is advantageous to close unused FILE pointers. These closed descriptors may be reassigned in a subsequent fopen call.

String input/output

Now I/O from/to a string. The concepts are similar. Use the sscanf and sprintf calls.
   sscanf(str, control string, &var1, &var2, ...);
   sprintf(str, control string, expr1, expr2, ...);

Example: Here is a simple sscanf example:

   char str[] = "53 -123.456 @";
   int a;
   float b;
   char c;

   sscanf(str,"%d %f %c", &a, &b, &c);
   printf("a = %d\nb = %f\nc = %d\n", a, b, c);

This snippet generates the output:

   a = 53
   b = -123.456001
   c = 64

Example: Here is a simple sprintf example:

   char str[128];

   sprintf(str, "%lu %e\n", 521lu << 9 , 521.0 * 512.0);
   fprintf(stdout, "%s", str);

The output is

   266752 2.667520e+05

Example: Now here is a deeply illustrating example:

   int a = -3, b = 5;
   char str[128], *cptr;

   cptr = str;
   cptr += sprintf(cptr,"expression1 = %d, and ", a / (a + b));
   cptr += sprintf(cptr,"expression2 = %f.\n", (float)a / (float)(a + b));
   cptr += sprintf(cptr,"That's all!\n");
   printf("%s", str);

You get the output:

   expression1 = -1, and expression2 = -1.500000.
   That's all!

Don't ask us to explain now what this code does. Let us wait till you mature as a C programmer in order to understand, assimilate and eventually appreciate the big idiosyncracies of C, its pointer arithmetic, its arrays, bla bla bla. There is no hurry indeed.

Oh, didn't I mention that like printf, both fprintf and sprintf return the number of characters printed? Furthermore, each of scanf, fscanf and sscanf returns an integer value. Read your system's manual if you have to know what this return value stands for.

Formatted input/output

You can control the format of printed output using special directives. Using these extra directives helps you, for example, to generate nicely aligned lines. All you have to do is to insert a number between the % and the subsequent type specifier (d,x,f,s, etc.). The following table summarizes some of these options. Here n and m are assumed to be positive integer values.

FormatDescription
%nd,%ni,
%nu,%nld,
%nLd
, etc.

Print an integer in the decimal notation using at least n characters. If the decimal representation of the integer is of length l < n (including the sign for negative integers), then n - l  spaces are printed and then the integer is printed. If l >= n, then this directive is similar to the simple %d.

%-nd,%-ni,
%-nu
, etc.

This is similar to %nd except that the extra spaces, if any, are printed after the integer. In short, %nd yields right-justified output, whereas %-nd yields left-justified output.

%no,%-noSame as %nd and %-nd, except that the integer is printed in octal.
%nx,%-nx,
%nX,%-nX
Same as %nd and %-nd, except that the integer is printed in hexadecimal.
%n.mf,%n.mlf,
%n.mLf

Print a right-justified real number (in the decimal notation) with a total of n characters (including the decimal pointer and the sign) and with m characters to the right of the decimal point. If the float value cannot be printed in the recommended space, then %n.mf prints as %f does.

%-n.mf,%-n.mlf,
%-n.mLf

Same as %n.mf, except that the printing is left-justified.

%ns

Print a right-justified string using a total of n characters. If the original string is bigger than or equal to the recommended number n, then %ns prints as does %s.

%-ns

This is the same as %ns except that the output is left-justified, i.e., extra spaces, if any, are printed after the string.

Example: For the following formatted print statements

   printf("{%2d} {%3d} {%4d} {%-2d} {%-3d} {%-4d}\n",
          123, 234, 345, 456, 567, 678);
   printf("{%2x} {%3x} {%4x} {%-2x} {%-3x} {%-4x}\n",
          123, 234, 345, 456, 567, 678);
   printf("{%2s} {%3s} {%4s} {%-2s} {%-3s} {%-4s}\n",
          "abc", "bcd", "cde", "def", "efg", "fgh");
   printf("{%4.2f} {%5.2f} {%6.2f} {%-5.2f} {%-6.2f} {%-7.2f}\n",
          1.2345, 2.3456, 3.4567, -4.5678, -5.6789, -6.7890);

the output looks like:

   {123} {234} { 345} {456} {567} {678 }
   {7b} { ea} { 159} {1c8} {237} {2a6 }
   {abc} {bcd} { cde} {def} {efg} {fgh }
   {1.23} { 2.35} {  3.46} {-4.57} {-5.68 } {-6.79  }

Example: io1.c

The program

#include <stdio.h>

main ()
{
   char name1[64] = "Abhijit Das",
        name2[64] = "Chittaranjan Mandal",
        name3[64] = "Sandeep Sen";
   char dept1[4] = "CSE", dept2[4] = "SIT", dept3[4] = "CSE";
   int room1 = 123, room2 = 6, room3 = 301;
   float height1 = 1.7781, height2 = 1.7399, height3 = 1.7412;
   int lucky1[2] = { 561, 1729 },
       lucky2[2] = { 28, 496 },
       lucky3[2] = { -1073741789, 104729};

   printf("   %10s %20s  %s", "Name", "Department", "Room No");
   printf("  Height        Lucky numbers\n");
   printf("  +-------------------------------------------------------------------------+\n");
   printf("  | %-20s", name1);
   printf("%7s   ",dept1);
   printf("    %-2d",room1);
   printf("%9.2f",height1);
   printf("   %11d and %-7d|", lucky1[0], lucky1[1]);
   printf("\n");
   printf("  | %-20s", name2);
   printf("%7s   ",dept2);
   printf("     %-2d",room2);
   printf("%9.2f",height2);
   printf("   %11d and %-7d|", lucky2[0], lucky2[1]);
   printf("\n");
   printf("  | %-20s", name3);
   printf("%7s   ",dept3);
   printf("    %-3d",room3);
   printf("%9.2f",height3);
   printf("   %11d and %-7d|", lucky3[0], lucky3[1]);
   printf("\n");
   printf("  +-------------------------------------------------------------------------+\n");
}

The output

         Name           Department  Room No  Height        Lucky numbers
  +-------------------------------------------------------------------------+
  | Abhijit Das             CSE       123     1.78           561 and 1729   |
  | Chittaranjan Mandal     SIT        6      1.74            28 and 496    |
  | Sandeep Sen             CSE       301     1.74   -1073741789 and 104729 |
  +-------------------------------------------------------------------------+


Course home