Accept assignment and create repository https://classroom.github.com/a/_5Ho1rfv

Deadline: Friday 9/18 at 11:00PM.

Objectives

The goal of this module are to:

  • 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.

NOTE: When using scanf to read “y” or “n” make sure to put a space before the %c in the scanf command especially if you have another read form input statement before, concretely: scanf(" %c", &newInput).

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 subtlety 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 and Roxana Leontie 2020.