Lab 2: Input/Output
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 forscanf
. 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 toprintf()
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 namedpatients.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 calledtest2.c
. -
The program is
cp
and the commandline arguments are the stringstest.c
andtest2.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 howgetc()
reads a char at a time from a file. You can similarly write a char at a time using the functionputc(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.