CS202 Lecture notes -- Output

  • James S. Plank
  • Directory: ~jplank/cs202/Notes/Output
  • Lecture notes: http://web.eecs.utk.edu/~jplank/plank/classes/cs202/Notes/Output
  • Original Notes: 2009 or so, maybe.
  • Last modification date: Tue Aug 24 16:40:56 EDT 2021

    Cout and cerr

    When your program starts running, the operating system sets up two separate output streams for you. They are called "standard output" and "standard error." You write to them using cout and cerr respectively. For example, the program src/coutcerr.cpp writes one line to cout and one to cerr:

    /* A program that writes to the two default output streams: 
    
       - cout to standard output
       - cerr to standard error. */
    
    #include <iostream>
    using namespace std;
    
    int main()
    {
      cout << "Writing this to cout.\n";
      cerr << "Writing this to cerr.\n";
      return 0;
    }
    

    By default both standard output and standard error go to the terminal, so running this without any file redirection results in both lines going to the terminal:

    UNIX> bin/coutcerr
    Writing this to cout.
    Writing this to cerr.
    UNIX> 
    
    However, if you redirect standard output to a file, you see that standard error still goes to the terminal. This is nice because you can alert the user to erroneous conditions even he or she is redirecting standard output to a file:
    UNIX> bin/coutcerr > output.txt
    Writing this to cerr.
    UNIX> cat output.txt
    Writing this to cout.
    UNIX> 
    
    Shells differ on how to redirect both standard output and standard error to a file. to a file with ">&":
    UNIX> bin/coutcerr >& output.txt
    UNIX> cat output.txt
    Writing this to cout.
    Writing this to cerr.
    UNIX> 
    

    Printf() with numbers

    While cout is convenient, it is also a pain for formatted output. When I want to format my output precisely, I use the C formatting procedure printf(). Printf() is a weird procedure as it can take a variable number of parameters. The first parameter is always a formatting string. In its simplest form, the formatting string has nothing special about it, and it's printed on standard output (in src/hwpf.cpp):

    /* This program uses printf() instead of cout to print hello world. */
    
    #include <cstdio>
    
    int main()
    {
      printf("Hello world\n");
      return 0;
    }
    

    You'll note that I included <cstdio> instead of <iostream>, because printf() is defined in the C stdio library. I also omitted the "using namespace std", because the only thing I'm using in this program is printf(), and it is part of C and not C++. Typically, though, you use so much from the "std" namespace that you have to include the "using namespace std" statement. The output of this program is pretty obvious:

    UNIX> bin/hwpf
    Hello world
    UNIX> 
    
    You may specify that printf() print additional arguments by putting a percent sign in the formatting string with more special formatting commands. For example, putting "%d" in the formatting string specifies to print the next parameter as an integer. The program src/ints1.cpp shows a for() loop where each printf() statement prints three integers, i, i2, and 2i:

    /* Use printf() to print out each number, i, from 0 to 10, 
       along with its square and its double. */
       
    #include <cstdio>
    using namespace std;
    
    int main()
    {
      int i, j;
    
       for (i = 0; i <= 10; i++) {
         j = i*i;
         printf("I is %d - i*i is %d - i*2 is %d\n", i, j, i*2);
       }
       return 0;
    }
    

    The output is straightforward:

    UNIX> bin/ints1
    I is 0 - i*i is 0 - i*2 is 0
    I is 1 - i*i is 1 - i*2 is 2
    I is 2 - i*i is 4 - i*2 is 4
    I is 3 - i*i is 9 - i*2 is 6
    I is 4 - i*i is 16 - i*2 is 8
    I is 5 - i*i is 25 - i*2 is 10
    I is 6 - i*i is 36 - i*2 is 12
    I is 7 - i*i is 49 - i*2 is 14
    I is 8 - i*i is 64 - i*2 is 16
    I is 9 - i*i is 81 - i*2 is 18
    I is 10 - i*i is 100 - i*2 is 20
    UNIX> 
    
    However, it is ugly. It's nicer if you can make sure that all those ints print with the same number of characters so that they line up. You do that by putting a width specification in front of the 'd' in "%d" (in src/ints2.cpp):

    /* This is the same as ints1.cpp, except:
    
         - We print i and 2*i with a field width of two characters.
         - We print i*i with a field width of three characters.
    
       In that way, they all line up in nice, neat columns. */   
    
    #include <cstdio>
    using namespace std;
    
    int main()
    {
      int i, j;
    
      for (i = 0; i <= 10; i++) {
        j = i*i;
        printf("I is %2d - i*i is %3d - i*2 is %2d\n", i, j, i*2);
    //               ^^^          ^^^          ^^^
      }
      return 0;
    }
    

    This output is much nicer:

    UNIX> bin/ints2
    I is  0 - i*i is   0 - i*2 is  0
    I is  1 - i*i is   1 - i*2 is  2
    I is  2 - i*i is   4 - i*2 is  4
    I is  3 - i*i is   9 - i*2 is  6
    I is  4 - i*i is  16 - i*2 is  8
    I is  5 - i*i is  25 - i*2 is 10
    I is  6 - i*i is  36 - i*2 is 12
    I is  7 - i*i is  49 - i*2 is 14
    I is  8 - i*i is  64 - i*2 is 16
    I is  9 - i*i is  81 - i*2 is 18
    I is 10 - i*i is 100 - i*2 is 20
    UNIX> 
    
    Each integer is printed with a given number of characters, right justified. Printing doubles is more problematic. (Oh, by the way, we don't use float's in this class. They are too prone to round-off error.) You specify a float with "%lf". By default, that prints the double to six decimal digits. If you want to specify widths, as we did with int's above, you use "%t.dlf", where t is the total width and d is the number of decimal digits. For example, the following program (src/pieln10.cpp) prints pi, e and the natual log of 10, each of which will have seven total digits, five after the decimal point:

    /* Print pi, e, and the natual log of 10, each with 7 total digits, 5 after the decimal point. */
    
    #include <cmath>
    #include <cstdio>
    using namespace std;
    
    int main()
    {
      double pi, e, ln10;
    
      pi   = acos(-1.0);  // I figure at this point, you might enjoy some high school math.
      e    = exp(1.0);    // exp(x) returns e to the x-th power
      ln10 = log(10.0);   // The poorly named log(x) returns the natural log of x.
     
      printf("pi:   %7.5lf\n", pi);
      printf("e:    %7.5lf\n", e);
      printf("ln10: %7.5lf\n", ln10);
    
      return 0;
    }
    

    Here's the output:

    UNIX> bin/pieln10
    pi:   3.14159
    e:    2.71828
    ln10: 2.30259
    UNIX> 
    
    How did I know what those math functions were? They are part of the math library of C, which is defined there in <cmath>. You can see a listing of the functions at https://en.wikipedia.org/wiki/C_mathematical_functions.

    Printf() with strings, left-justifying

    The handling of strings is a little cumbersome because printf() doesn't handle C++ strings. Instead, it handles C-style strings, which are character arrays that are null-terminated. In other words, their last character is the character '\0'. You won't need to concern yourself with the intricacies of C-style strings until you take CS360. Just remember the following three rules:
    1. String literals, like "Hello World", are interpreted by the compiler as C-style strings.
    2. You can convert a C++ string into a C-style string by invoking the method c_str().
    3. You can convert a C-style string to a C++ string with simple assignment or even type-casting. (I don't do that in this example, but I will very soon).
    I'll illustrate with an example that uses printf() to print strings. Suppose you have a file that contains first names, last names and social security numbers, such as data/info-file.txt

    Charlie Crossway 286-50-9109
    Molly Needham 825-03-8649
    Molly Nashville 543-73-8293
    Cindy Endgame 003-12-8676
    Tamika Marty 230-97-5641
    John Scar 724-14-6244
    Lazar Argive 258-28-8603
    Blanche Subterfuge 790-84-5341
    Raynoch Sarsaparilla 151-05-7446
    Baine Junta 761-03-0452
    

    And suppose you want to print it more nicely, so that first names, last names, and SSN's are in their own columns. Here's some code to do it (src/ssn-reader.cpp):

    /* This program reads three words at a time -- first name, last name, and social
       security number (as a string, not as a number).  It then prints them, each in
       its own column. */
    
    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    int main()
    {
      string fn, ln, ssn;
    
      while (cin >> fn >> ln >> ssn) {
        printf("%-10s %-13s %11s\n", fn.c_str(), ln.c_str(), ssn.c_str());
      }
      return 0;
    }
    

    With the width specification, I use negative numbers to left-justify the strings, and pad them to 10, 13 and 11 characters respectively:

    UNIX> bin/ssn-reader < data/info-file.txt
    Charlie    Crossway      286-50-9109
    Molly      Needham       825-03-8649
    Molly      Nashville     543-73-8293
    Cindy      Endgame       003-12-8676
    Tamika     Marty         230-97-5641
    John       Scar          724-14-6244
    Lazar      Argive        258-28-8603
    Blanche    Subterfuge    790-84-5341
    Raynoch    Sarsaparilla  151-05-7446
    Baine      Junta         761-03-0452
    UNIX> 
    

    Chars, octal and hexadecimal

    Three additional format specifications are "%c" to print characters, "%o" to print octal, and "%x" to print hexadecimal. First, the ASCII character set is a mapping from integers to printable characters. For example, 'A' is 65, 'a' is 97 and '0' is 48. Using "%c" says to print an integer value as a character, using its ASCII value. An example is in src/printchars.cpp, which prints out all characters from 32 to 127:

    /* Print the character values of numbers from 32 to 127, in eight columns. */
    
    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    int main()
    {
      int i, j;
    
      for (i = 32; i < 128; i += 8) {
        for (j = 0; j < 8; j++) {
          printf("%3d: '%c'  ", i+j, i+j);
        }
        printf("\n");
      }
      return 0;
    }
    

    Observe that i is an int and not a char, but the code works fine. The reason it works is because "%c" prints the character based on the ASCII value of a number, and char's and int's are both numbers.

    Here's the output:

    UNIX> bin/printchars
     32: ' '   33: '!'   34: '"'   35: '#'   36: '$'   37: '%'   38: '&'   39: '''  
     40: '('   41: ')'   42: '*'   43: '+'   44: ','   45: '-'   46: '.'   47: '/'  
     48: '0'   49: '1'   50: '2'   51: '3'   52: '4'   53: '5'   54: '6'   55: '7'  
     56: '8'   57: '9'   58: ':'   59: ';'   60: '<'   61: '='   62: '>'   63: '?'  
     64: '@'   65: 'A'   66: 'B'   67: 'C'   68: 'D'   69: 'E'   70: 'F'   71: 'G'  
     72: 'H'   73: 'I'   74: 'J'   75: 'K'   76: 'L'   77: 'M'   78: 'N'   79: 'O'  
     80: 'P'   81: 'Q'   82: 'R'   83: 'S'   84: 'T'   85: 'U'   86: 'V'   87: 'W'  
     88: 'X'   89: 'Y'   90: 'Z'   91: '['   92: '\'   93: ']'   94: '^'   95: '_'  
     96: '`'   97: 'a'   98: 'b'   99: 'c'  100: 'd'  101: 'e'  102: 'f'  103: 'g'  
    104: 'h'  105: 'i'  106: 'j'  107: 'k'  108: 'l'  109: 'm'  110: 'n'  111: 'o'  
    112: 'p'  113: 'q'  114: 'r'  115: 's'  116: 't'  117: 'u'  118: 'v'  119: 'w'  
    120: 'x'  121: 'y'  122: 'z'  123: '{'  124: '|'  125: '}'  126: '~'  127: ''  
    UNIX> 
    
    One nice thing about ASCII is that the upper case, lower case and decimal characters are all contiguously numbered. Therefore, you can do math on the numbers with logical results. For example, if i is a lower case letter, you can convert it to an upper case letter with: i + ('A'-'a').

    When you specify a character, such as 'A' in a program, the compiler simply turns it into its ASCII integer. Thus, 'A' really equals 65. It's just easier to read it as 'A'.

    The lower ASCII values are some of the weirder characters, like TAB, BACKSPACE and NEWLINE.

    There are two other ways to print out integers -- as octal with "%o", and as hexadecimal with "%x". By convention, when you specify an integer with a preceding '0', the C++ compiler treats it as octal, or base eight. When you specify an integer with a preceding "0x", the C++ compiler treats it as hexadecimal, or base sixteen, with the characters 'a' through 'f' corresponding to the digits from 10 through 15.

    Look at an example program in src/weirdints.cpp:

    /* This program demonstrates octal and hexadecimal numbers, both as
       literals in C++, and when printed with printf(). */
    
    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    int main()
    {
      int i, j;
    
      printf(" 010 is equal to: %2d   0%02o  0x%02x\n", 010, 010, 010);
      printf("0x10 is equal to: %2d   0%02o  0x%02x\n", 0x10, 0x10, 0x10);
    
      printf("\n");
    
      /* Print the numbers from 0 to 31 in octal and in hex.  
         Print four numbers per row. */
    
      for (i = 0; i < 32; i++) {
        printf("    %2d  0%02o  0x%02x", i, i, i);
        if (i%4 == 3) printf("\n");
      }
      return 0;
    }
    

    The first line shows that "010" is interpreted by the C++ compiler as 10 in octal, which is equal to 8 in decimal, and 0x8 in hexadecimal. The second line shows that "0x10" is interpreted as 10 in hexadecimal, which is 16 in decimal, and 20 in octal. The remaining lines show the values from 0 to 31 in decimal, octal and hexadecimal:

    UNIX> bin/weirdints
     010 is equal to:  8   010  0x08
    0x10 is equal to: 16   020  0x10
    
         0  000  0x00     1  001  0x01     2  002  0x02     3  003  0x03
         4  004  0x04     5  005  0x05     6  006  0x06     7  007  0x07
         8  010  0x08     9  011  0x09    10  012  0x0a    11  013  0x0b
        12  014  0x0c    13  015  0x0d    14  016  0x0e    15  017  0x0f
        16  020  0x10    17  021  0x11    18  022  0x12    19  023  0x13
        20  024  0x14    21  025  0x15    22  026  0x16    23  027  0x17
        24  030  0x18    25  031  0x19    26  032  0x1a    27  033  0x1b
        28  034  0x1c    29  035  0x1d    30  036  0x1e    31  037  0x1f
    UNIX> 
    
    Octal is useful as a way to show bits in groups of three. For example, 0644 is equal to 110 100 100 in binary. Sometimes that is convenient.

    Hexadecimal is much more convenient, because 16 = 24, which means that you can represent a byte with exactly two digits in hexadecimal. For that reason, hexadecimal is used to represent pointers in programs (You'll see more of that later in your career). As another example, sometimes computer routers are identified by six-byte ID numbers. Often you see these id's represented by bytes in hexadecimal separated by colons, such as "00:19:e3:d8:5c:64". The hexadecimal makes reading the bytes convenient.

    A final detail of src/weirdints.cpp -- the printf() statement is:

        printf("    %2d  0%02o  0x%02x", i, i, i);
    

    When you put a zero in front of a width specification, that says to pad the number to the given number of digits, and put in leading zeros instead of spaces. Without that specification, the number zero would have been printed as "0x 0" instead of "0x00".