Create your repository: https://classroom.github.com/a/JZmorQ_G
Then clone the repository into your CodeAnywhere container using git clone YOUR_URL
(Click the green "Clone or Download" button to find your repository's URL).
Create all the exercise files inside your repository directory and then add, commit, and push them to GitHub when you are finished.
Deadline: Friday 9/14 11:59PM

Objectives

The goals of this module are:

  • Learn how to get input from users, command line arguments, and files.


Screen I/O

Screen input and output in C:

  • We have already seen examples of screen output using printf.

  • The rules for printf are somewhat cryptic initially, but are easy to learn.

  • Screen input uses scanf.

  • Other than simple numbers and text lines, screen input can be more complicated.
An example:
int main ()
{
  // Some variables: an int, a double, a char and a string.
  int i = 1;
  double x = 2.5;
  char ch = 'a';
  char *str = "hello";

  // A char array to hold input lines.
  char inputLine [100];

  // Print the four variables.
  printf ("i = %d  x=%lf  ch=%c  str = %s\n", i, x, ch, str);

  // Scanning in numbers:
  printf ("Enter an integer followed by a double: ");
  scanf ("%d %lf", &i, &x);
  printf ("i = %d   x = %lf\n", i, x);

  // Scanning in a string:
  printf ("Enter a string: ");
  scanf ("%s", inputLine);
  printf ("You entered: %s\n", inputLine);
}
Lab 2.1: Implement a program in screenExample.c that reads in a names as a string, age as an integer, and weight as a float. Then print out what was read.

Note:

  • Reading from the screen is done using scanf.

  • The same data-type specifiers used for printf are used for scanf. Look back at Module 0 for a cheat sheet.

  • To read "into" variables, scanf() first uses the format-specifier to let you tell it the types of variables you want values read into:
      scanf ("%d %lf", &i, &x);         // Format string: an int, a double
    
    and then you pass the addresses of the variables:
      scanf ("%d %lf", &i, &x);         // Addresses of the int i, double x
    
    This allows scanf() to place the values directly into those variables.

  • To read a string, you have to create sufficient space for the input string ahead of time.
    • Suppose, in the above example, we'd only declared
        // A char array to hold input lines.
        char inputLine [5];
      
    • Then, suppose the user typed "hello, there".
    • It would not work, because
        scanf ("%s", str);   // str has only 5 chars of space.
      

  • In reading a string, scanf appends the end-of-string character '\0' to the string.


File I/O

Let's modify the above example to also write the data to a file called "data.txt"

int main ()
{
  // Some variables: an int, a double, a char and a string.
  int i = 1;
  double x = 2.5;
  char ch = 'a';
  char *str = "hello";

  // A char array to hold input lines.
  char inputLine [100];

  // Declare a file pointer. Note the capitalization.
  FILE *dataFile;

  // Open the file.
  dataFile = fopen ("data.txt", "w");

  // Print the four variables.
  printf ("i = %d  x=%lf  ch=%c  str = %s\n", i, x, ch, str);
  fprintf (dataFile, "i = %d  x=%lf  ch=%c  str = %s\n", i, x, ch, str);  // Write to file.

  // Scanning in numbers:
  printf ("Enter an integer followed by a double: ");
  scanf ("%d %lf", &i, &x);
  printf ("i = %d   x = %lf\n", i, x);
  fprintf (dataFile, "i = %d   x = %lf\n", i, x);                         // Write to file.

  // Scanning in a string:
  printf ("Enter a string: ");
  scanf ("%s", inputLine);
  printf ("You entered: %s\n", inputLine);
  fprintf (dataFile, "You entered: %s\n", inputLine);                     // Write to file.

  // Close the file.
  fclose (dataFile);
}

Note:

  • The example shows how to write to a text file.
  • First, a so-called file handle needs to be declared:
      FILE *dataFile;
    
    This is a pointer to something that we can use to read or write to files.

  • A file (text or otherwise) is opened using fopen():
      dataFile = fopen ("data.txt", "w");
    
    • The first argument is the name of the file.
    • The second is a mode string that must be one of the following:
      "w" - for writing
      "r" - for reading
      "a" - for appending
      "b" - for a binary file.
    • ANSI C99 also allows:
      "r+" - for reading and writing
      "w+" - for reading and writing to a new file
      "a+" - for reading and appending
    • Both "w" and "w+" create a new file, overwriting a possibly existing file with the same name.
    • Modes can be combined as in:
         dataFile = fopen ("blah.txt", "rb");
      

  • The fprintf() method is more or less identical to printf() except for the first parameter, which is the file handle:
      fprintf (dataFile, "i = %d   x = %lf\n", i, x);
    

Lab 2.2: Implement a program in fileExample.c that extends your program from exercise 1. Your new program should prompt the user to enter a name as a string, age as an integer, and weight as a float. Instead of printing the results to the screen, it should *append* them to a file named patients.txt. After the user enters this information, the program should ask if they want to enter data for another patient; if the user enters a "y", then they should be prompted for the same information again, otherwise the program should exit.

Next, let's look at file input, by reading a text file byte by byte

#define MAX_CHARS_PER_LINE 100

int main ()
{
  int lineNumber;                           // We'll track line numbers.
  char inputLine [MAX_CHARS_PER_LINE + 1];  // Need an extra char for string-terminator.
  char ch;                                  // Variable into which we'll read each char.
  int cursorPosition;                       // Position in inputLine for current char.
  FILE *dataFile;                           // The file.

  // Open the file.
  dataFile = fopen ("fileExample.c", "r");

  // Initial value - first line will be "1".
  lineNumber = 0;

  // Initial position of cursor within inputLine array:
  cursorPosition = 0;

  // Get first character.
  ch = fgetc (dataFile);

  // Keep reading chars until EOF char is read.
  while (ch != EOF) {

    // If we haven't seen the end of a line, put char into current inputLine.
    if (ch != '\n') {

      // Check whether we have exceeded the space allotted.
      if (cursorPosition >= MAX_CHARS_PER_LINE) {
        // Can't append.
        printf ("Input line size exceeded - exiting program\n");
        exit (0);
      }

      // OK, there's space, so append to inputLine.
      inputLine[cursorPosition] = ch;
      cursorPosition ++;

    }
    else {

      // Need to place a string-terminator because that's not in the file.
      inputLine[cursorPosition] = '\0';

      // Print.
      lineNumber ++;
      printf ("Line %4d: %s\n", lineNumber, inputLine);

      // Reset cursor.
      cursorPosition = 0;

    }

    // Get next char.
    ch = fgetc (dataFile);

  } // end while


  // Done.
  fclose (dataFile);

}

Lab 2.3: Implement the above in fileExample2.c. Be sure you understand how it works!

Note:

  • The convention for constants is to write them in caps (MAX_CHARS_PER_LINE) and to use underscores to separate out "meaning".

  • The function fgetc is used to read the next char from the given stream.

  • Every file has a special EOF char at the end, upon whose detection we stop reading the file:
      while (ch != EOF) {
    
        // ...
    
      }
    

  • Note how you can terminate execution by calling exit(0).

There's a sublety to be aware of with regard to the end of a line:

  • Unix files use a single character, \n (line feed), to mark the end of a line in a text file.

  • Windows (DOS) uses two characters, linefeed followed by carriage-return, in text files.

  • This is why, when you copy over a Windows file to Unix, you sometimes see ^M at the end of every line.

  • The problem is partially solved in that C libraries for Windows strip out the carriage-return when reading and append a carriage-return while writing to text files.

  • However, this filtering is only done for text files. Thus, while the above code will work on Windows, an equivalent byte-oriented program will not.

  • If you read (using fread, for example) or write byte-files that are also used as text files, you need to do your own filtering on Windows.


Commandline arguments

An example that uses commandline arguments:

  • Consider a Unix program like cp:
      cp test.c test2.c
    
    Here, the file test.c is copied into a new file called test2.c.

  • The program is cp and the commandline arguments are the strings test.c and test2.c.

A C program's main() function can receive arguments from the command-line:

// argc: number of commandline arguments.
// argv: array of strings.

int main (int argc, char **argv)
{
  if (argc != 3) {
    printf ("Usage: copy [source-file] [destination-file]\n");
    exit (0);
  }

  printf ("Copying from %s to %s ... \n", argv[1], argv[2]);

  // ... Do actual copying ...

  printf ("... done\n");
}
Note:
  • The first string is always the program name itself (in argv[0]).

  • This is why we check whether the number of arguments argc is 3.

Lab 2.4: In copy.c use the template above and add in the code to perform data copying between the two files named as arguments when running the program. You've already seen how getc() reads a char at a time from a file. You can similarly write a char at a time using the function putc (char, FILE). You do not need to write EOF to the destination file, but you do need to close the file when you reach the end.

© 2003, Rahul Simha (revised 2017). Further revised by Tim Wood 2018.