Assignment #4

Introductory C Programming

UW Experimental College


Assignment #4

Handouts:

Assignment #4
Answers to Assignment #3
Class Notes, Chapters 6, 7, and 8
``Expressions and Statements'' handout

Reading Assignment:

Class Notes, Chapter 6
Class Notes, Chapter 7 (Sec. 7.3 is a bit advanced, but do at least skim it)
Class Notes, Chapter 8

Review Questions:

  1. What would the expression
    	c = getchar() != EOF
    
    do?
  2. Why must the variable used to hold getchar's return value be type int?
  3. What is the difference between the prefix and postfix forms of the ++ operator?
  4. (trick question) What would the expression
    	i = i++
    
    do?
  5. What is the definition of a string in C?
  6. (Advanced) What will the getline function from section 6.3 of the notes do if successive calls to getchar return the four values 'a', 'b', 'c', and EOF? Is getline's behavior reasonable? Is it possible for this situation to occur, that is, for a ``line'' somehow not to be terminated by \n?


Tutorial Section:

  1. Here is a toy program for computing your car's gas mileage. It asks you for the distance driven on the last tank of gas, and the amount of gas used (i.e. the amount of the most recent fill-up). Then it simply divides the two numbers.
    #include <stdio.h>
    #include <stdlib.h>	/* for atoi() */
    
    int getline(char [], int);
    
    int main()
    {
    	char inputline[100];
    	float miles;
    	float gallons;
    	float mpg;
    
    	printf("enter miles driven:\n");
    	getline(inputline, 100);
    	miles = atoi(inputline);
    
    	printf("enter gallons used:\n");
    	getline(inputline, 100);
    	gallons = atoi(inputline);
    
    	mpg = miles / gallons;
    	printf("You got %.2f mpg\n", mpg);
    
    	return 0;
    }
    
    int getline(char line[], int max)
    {
    int nch = 0;
    int c;
    max = max - 1;			/* leave room for '\0' */
    
    while((c = getchar()) != EOF)
    	{
    	if(c == '\n')
    		break;
    
    	if(nch < max)
    		{
    		line[nch] = c;
    		nch = nch + 1;
    		}
    	}
    
    if(c == EOF && nch == 0)
    	return EOF;
    
    line[nch] = '\0';
    return nch;
    }
    
    For each of the two pieces of information requested (mileage and gallons) the code uses a little three-step procedure: (1) print a prompt, (2) read the user's response as a string, and (3) convert that string to a number. The same character array variable, inputline, can be to hold the string used both times, because we don't care about keeping the string around once we've converted it to a number. The function for converting a string to an integer is atoi. (The compiler then automatically converts the integer returned by atoi into a floating-point number to be stored in miles or gallons. There's another function we could have used, atof, which converts a string--possibly including a decimal point--directly into a floating-point number.)

    You might wonder why we're reading the user's response as a string, only to turn around and convert it to a number. Isn't there a way to read a numeric response directly? There is (one way is with a function called scanf), but we're going to do it this way because it will give us more flexibility later. (Also, it's much harder to react gracefully to any errors by the user if you're using scanf for input.)

    Obviously, this program would be a nuisance to use if you had several pairs of mileages and gallonages to compute. You'd probably want the program to prompt you repetitively for additional pairs, so that you wouldn't have to re-invoke the program each time. Here is a revised version which has that functionality. It keeps asking you for more distances and more gallon amounts, until you enter 0 for the mileage.
    #include <stdio.h>
    #include <stdlib.h>	/* for atoi() */
    
    int getline(char [], int);
    
    int main()
    {
    	char inputline[100];
    	float miles;
    	float gallons;
    	float mpg;
    
    	for(;;)
    		{
    		printf("enter miles driven (0 to end):\n");
    		getline(inputline, 100);
    		miles = atoi(inputline);
    
    		if(miles == 0)
    			break;
    
    		printf("enter gallons used:\n");
    		getline(inputline, 100);
    		gallons = atoi(inputline);
    
    		mpg = miles / gallons;
    		printf("You got %.2f mpg\n\n", mpg);
    		}
    
    	return 0;
    }
    
    The (slightly) tricky part about writing a program like this is: what kind of a loop should you use? Conceptually, it seems you want some sort of a while loop along the lines of ``while(miles != 0)''. But that won't work, because we don't have the value of miles until after we've prompted the user for it and read it in and converted it.

    Therefore, the code above uses what at first looks like an infinite loop: ``for(;;)''. A for loop with no condition tries to run forever. But, down in the middle of the loop, if we've obtained a value of 0 for miles, we execute the break statement which forces the loop to terminate. This sort of situation (when we need the body of the loop to run part way through before we can decide whether we really wanted to make that trip or not) is precisely what the break statement is for.
  2. Next, we're going to write our own version of the atoi function, so we can see how it works (or might work) inside. (I say ``might work'' because there's no guarantee that your compiler's particular implementation of atoi will work just like this one, but it's likely to be similar.)
    int myatoi(char str[])
    {
    	int i;
    	int digit;
    	int retval = 0;
    
    	for(i = 0; str[i] != '\0'; i = i + 1)
    		{
    		digit = str[i] - '0';
    		retval = 10 * retval + digit;
    		}
    
    	return retval;
    }
    
    You can try this function out and work with it by rewriting the gas-mileage program slightly to use it. (Just replace the two calls to atoi with myatoi.)

    Remember, the definition of a string in C is that it is an array of characters, terminated by '\0'. So the basic strategy of this function is to look at the input string, one character at a time, from left to right, until it finds the terminating '\0'.

    The characters in the string are assumed to be digits: '1', '5', '7', etc. Remember that in C, a character's value is the numeric value of the corresponding character in the machine's character set. It turns out that we can write this string-to-number conversion code without knowing what character set our machine uses or what the values are. (If you're curious, in the ASCII character set which most machines use, the character '0' has the value 48, '1' has the value 49, '2' has the value 50, etc.)

    Whatever value the character '0' has, '0' - '0' will be zero. (Anything minus itself is 0.) In any realistic character set, '1' has a value 1 greater than '0', so '1' - '0' will be 1. Similarly, '2' - '0' will be 2. So we can get the ``digit'' value of a character (on the range 0-9) by subtracting the value of the character '0'. Now all we have to do is figure out how to combine the digits together into the final value.

    If you look at the number 123 from left to right, the first thing you see is the digit 1. You might imagine that you'd seen the whole number, until you saw the digit 2 following it. So now you've seen the digits 1 2, and how you can get the number 12 from the digits 1 and 2 is to multiply the 1 by 10 and add the 2. But wait! There's still a digit 3 to come, but if you multiply the 12 by 10 and add 3, you get 123, which is the answer you wanted. So the algorithm for converting digits to their decimal value is: look at the digits from left to right, and for each digit you find, multiply the number you had before by 10 and add in the digit you just found. (When the algorithm begins, ``the number you had before'' starts out as 0.) When you run out of digits, you're done. (The code above uses a variable named retval to keep track of ``the number you had before'', because it ends up being the value to be returned.)

    If you're not convinced that this algorithm will work, try adding the line
    	printf("digit = %d, retval = %d\n", digit, retval);
    
    at the end of the loop, so you can watch it as it runs.

    The code above works correctly as long as the string contains digits and only digits. But if the string contained, say, the letter 'Q', the code would compute the value 'Q' - '0', which would be a meaningless number. So we'd like to have the code do something reasonable if it should happen to encounter any non-digits (that is, if someone should happen to call it with a string containing non-digits).

    One way for the string to contain non-digits is if it contains any leading spaces. For example, the string " 123" should clearly be converted to the value 123, but our code above wouldn't be able to do so. One thing we need to do is skip leading spaces. And the other thing we'll do (which isn't perfect, but is a reasonable compromise) is that if we hit a non-space, non-digit character, we'll just stop (i.e. as if we'd reached the end of the string). This means that the call myatoi("123abc") will return 123, and myatoi("xyz") will return 0. (As it happens, the standard atoi function will behave exactly the same way.)

    Classifying characters (space, digit, etc.) is easy if we include the header file <ctype.h>, which gives us functions like isspace which returns true if a character is a space character, and isdigit which returns true if a character is a digit character.

    Putting this all together, we have:
    #include <ctype.h>
    
    int myatoi(char str[])
    {
    	int i;
    	int retval = 0;
    
    	for(i = 0; str[i] != '\0'; i = i + 1)
    		{
    		if(!isspace(str[i]))
    			break;
    		}
    
    	for(; str[i] != '\0'; i = i + 1)
    		{
    		if(!isdigit(str[i]))
    			break;
    		retval = 10 * retval + (str[i] - '0');
    		}
    
    	return retval;
    }
    
    (You may notice that I've deleted the digit variable in this second example, because we didn't really need it.)

    There are now two loops. The first loop starts at 0 and looks for space characters; it stops (using a break statement) when it finds the first non-space character. (There may not be any space characters, so it may stop right away, after making zero full trips through the loop. And the first loop doesn't do anything except look for non-space characters.) The second loop starts where the first loop left off--that's why it's written as
    	for(; str[i] != '\0'; i = i + 1)
    
    The second loop looks at digits; it stops early if it finds a non-digit character.

    The remaining problem with the myatoi function is that it doesn't handle negative numbers. See if you can add this functionality. The easiest way is to look for a '-' character up front, remember whether you saw one, and then at the end, after reaching the end of the digits and just before returning retval, negate retval if you saw the '-' character earlier.
  3. Here is a silly little program which asks you to type a word, then does something unusual with it.
    #include <stdio.h>
    
    extern int getline(char [], int);
    
    int main()
    {
    	char word[20];
    	int len;
    	int i, j;
    
    	printf("type something: ");
    	len = getline(word, 20);
    
    	for(i = 0; i < 80 - len; i++)
    		{
    		for(j = 0; j < i; j++)
    			printf(" ");
    		printf("%s\r", word);
    		}
    	printf("\n");
    
    	return 0;
    }
    (To understand how it works, you need to know that \r prints a carriage return without a linefeed.) Type it in and see what it does. (You'll also need a copy of the getline function.) See if you can modify the program to move the word from right to left instead of left to right.


Exercises:

  1. Type in the character-copying program from section 6.2 of the notes, and run it to see how it works. (When you run it, it will wait for you to type some input. Type a few characters, hit RETURN; type a few more characters, hit RETURN again. Hit control-D or control-Z when you're finished.)
  2. Type in the getline() function and its test program from section 6.3 of the notes, and run it to see how it works. You can either place getline() and its test program (main()) in one source file or, for extra credit, place getline() in a file getline.c and the test program in a file getlinetest.c (or perhaps gtlntst.c), for practice in compiling a program from two separate source files.
  3. Rewrite the getline test program from Exercise 2 to use the loop
    	while(getline(line, 256) != EOF)
    		printf("%s\n", line);
    
    That is, have it simply copy a line at a time, without printing ``You typed''. Compare the behavior of the character-copying and line-copying programs. Do they behave differently?

    Now rewrite the character-copying program from Exercise 1 to use the loop
    	while((c = getchar()) != EOF)
    		printf("you typed '%c'\n", c);
    
    and try running it. Now do things make more sense?
  4. The standard library contains a function, atoi, which takes a string (presumably a string of digits) and converts it to an integer. For example, atoi("123") would return the integer 123.

    Write a program which reads lines (using getline), converts each line to an integer using atoi, and computes the average of all the numbers read. (Like the example programs in the notes, it should determine the end of ``all the numbers read'' by checking for EOF.) See how much of the code from assignment 3, exercise 7 you can reuse (if you did that exercise). Remember that integer division truncates, so you'll have to declare some of your variables as float or double.

    For extra credit, also compute the standard deviation (see assignment 3, exercise 7).
  5. Write a rudimentary checkbook balancing program. It will use getline to read a line, which will contain either the word "check" or "deposit". The next line will contain the amount of the check or deposit. After reading each pair of lines, the program should compute and print the new balance. You can declare the variable to hold the running balance to be type float, and you can use the function atof (also in the standard library) to convert an amount string read by getline into a floating-point number. When the program reaches end-of-file while reading "check" or "deposit", it should exit. (In outline, the program will be somewhat similar to the average-finding program.)

    For example, given the input
    	deposit
    	100
    	check
    	12.34
    	check
    	49.00
    	deposit
    	7.01
    
    the program should print something like
    	balance: 100.00
    	balance: 87.66
    	balance: 38.66
    	balance: 45.67
    


    Extra credit: Think about how you might have the program take the word "check" or "deposit", and the amount, from a single line (separated by whitespace).
  6. Rewrite the ``compass'' code from Assignment 2 (exercise 4) to use strcpy and strcat to build the "northeast", "southwest", etc. strings. (Don't worry about capitalizing them carefully.) You should be able to write it in a cleaner way, without so many if's and else's. Remember to declare the array in which you build the string big enough to hold the largest string you build (including the trailing \0).
  7. Write a program to read its input, one character at a time, and print each character and its decimal value.
  8. Write a program to read its input, one line at a time, and print each line backwards. To do the reversing, write a function
    	int reverse(char line[], int len)
    	{
    	...
    	}
    
    to reverse a string, in place. (It doesn't have to return anything useful.)


This page by Steve Summit // Copyright 1995-9 // mail feedback