Assignment #4 Answers

Introductory C Programming

UW Experimental College

Assignment #4 ANSWERS


Question 1. What would the expression

	c = getchar() != EOF
do?

It would read one character and compare it to the constant EOF. If the character read was equal to EOF, it would set c to 0, otherwise (i.e. for any other character) it would set c to 1. What it would not do is read a character, assign it to c, and then test it against EOF, which is what you usually want to do, and which you need to write

	(c = getchar()) != EOF
to do.

Question 2. Why must the variable used to hold getchar's return value be type int?

So that it can reliably store the value EOF.

Variables of type char are typically 8 bits large, which means that they can hold 2<sup>8</sup>, or 256 different character values. Furthermore, on an 8-bit system, getchar can theoretically return characters having any of these 256 character values. However, getchar can also return a 257th value, EOF, which is not a character value but rather an indication that there are no more characters to get. You can no more reliably store getchar's 257 return values in a variable of type char than you can store 13 eggs in a carton that holds a dozen. If you tried to assign getchar's return value to a char, you could either mistake a real character value for EOF or EOF for a real character value, resulting either in premature termination of input or an infinite loop.

An int, on the other hand, is on the vast majority of machines larger than a char, so it can comfortably hold all 256 character values, plus EOF.

Question 3. What is the difference between the prefix and postfix forms of the ++ operator?

The prefix form increments first, and the incremented value goes on to participate in the surrounding expression (if any). The postfix form increments later; the previous value goes on to participate in the surrounding expression.

Question 4. What would the expression

	i = i++
do?

Nothing, or at least, nothing useful. Since it tries to modify i twice, it's undefined.

Question 5. What is the definition of a string in C?

An array of characters, terminated with the null character \0.

Question 6. What will the getline function do if successive calls to getchar return the four values 'a', 'b', 'c', and EOF?

The first three characters are placed in the line array, as usual, and when the EOF indicator is read, getline breaks out of its loop, also as usual. Although c is now EOF, nch is 3, so the condition c == EOF && nch == 0 is false. getline therefore does not return EOF, but rather terminates the line with \0 and returns its length, just as it does with a normal line which it finds \n at the end of.

Can this situation ever occur? One way to answer a question like this is not to try too hard to answer it, to err on the side of conservatism, to assume that if we can't prove that the unusual situation won't arise, we might as well be safe and arrange that our code can handle it if it somehow comes up. (There's obviously little or no harm in writing code to handle a situation that never comes up, while the reverse--neglecting to write code to handle a situation that does come up--can of course be very harmful.)

In any case, under Unix at least, this situation can in fact come up. Unix does not enforce any notion of a ``text file''; the sequence a, b, c, EOF at the end of a file is no more or less favored (by the operating system, that is) than the sequence a, b, c, \n, EOF. Furthermore, there are some programs (e.g. full-screen text editors such as EMACS) which make it easy to create a text file without a final newline (if only by accident). (But there are also examples of programs which inadvertently ignore the last line of a file whose last line does not end in \n, and this is a bug which can cause data loss.) So writing getline to treat a ``line'' ending in EOF (but no \n) is not only reasonable, but useful.

Tutorial 2. Improve the myatoi function so that it can handle negative numbers.

[You've seen the answer already, because I accidentally left the sign-handling code--within #ifdef SIGN--turned on in the copy of the code printed in the assignment. Oops.]

#include <ctype.h>

int myatoi(char str[])
{
	int i;
	int retval = 0;
	int negflag = 0;

	for(i = 0; str[i] != '\0'; i = i + 1)
		{
		if(!isspace(str[i]))
			break;
		}

	if(str[i] == '-')
		{
		negflag = 1;
		i = i + 1;
		}

	for(; str[i] != '\0'; i = i + 1)
		{
		if(!isdigit(str[i]))
			break;
		retval = 10 * retval + (str[i] - '0');
		}

	if(negflag)
		retval = -retval;

	return retval;
}

Tutorial 3. Modify the ``word zipping'' program to move the word from right to left instead of left to right.

#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 = 80 - len - 1; i >= 0; i--)
		{
		for(j = 0; j < i; j++)
			printf(" ");
		printf("%s \r", word);
		}
	printf("\n");

	return 0;
}

Exercise 4. Write a program which computes the average (and standard deviation) of a series of numbers.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

extern int getline(char [], int);

int main()
{
	char line[100];
	int x;
	double sum, sumsq;
	int n;
	double mean, stdev;

	sum = sumsq = 0.0;
	n = 0;

	while(getline(line, 100) != EOF)
		{
		x = atoi(line);
		sum = sum + x;
		sumsq = sumsq + x * x;
		n = n + 1;
		}

	mean = sum / n;
	stdev = sqrt((sumsq - sum * sum / n) / (n - 1));

	printf("mean: %f\n", mean);
	printf("std. dev.: %f\n", stdev);

	return 0;
}

Exercise 5. Write your own version of the atoi function.

[Obviously I should have removed this exercise when I added Tutorial 2. Apologies for the duplication.]

Here is a simple implementation:

int myatoi(char str[])
{
	int retval = 0;
	int i = 0;

	while(str[i] != '\0')
		{
		int digit = str[i] - '0';
		retval = 10 * retval + digit;
		i++;
		}

	return retval;
}
Remember that characters are represented by small integers representing their values in the machine's character set. We shouldn't have to know what these values are, but if we assume that the characters '0', '1', '2', ... '9' have consecutive values (which, as it happens, is a perfectly valid assumption) then subtracting the value of the character '0' from any digit character will give us that digit's value. For example, '1' - '0' is 1, '2' - '0' is 2, and of course '0' - '0' is 0. If str[i] is a digit character, then str[i] - '0' is its value. (In all of these subtractions, we are using the constant '0' to mean ``the value of the character '0','' which is, in fact, exactly what it means.)

The operation of the function is simple: it moves through the string from left to right, converting each digit character to a digit value and building up the return value. Each time we find another digit character, it (obviously) indicates that the number we're converting has another digit, so we multiply the number we've converted so far by 10, and add in the new digit. (Walk through this algorithm to convince yourself that it works. For example, if the string to be converted is "123", we'll make three trips through the loop, and retval will successively take on the values 1, 12, and finally 123.)

Here is a little test program to read strings from the user, convert them to numbers using myatoi, and print out the converted numbers:
#include <stdio.h>

int main()
{
	char line[100];
	int n;

	while(1)
		{
		printf("type a number:\n");
		if(getline(line, 100) == EOF)
			break;
		n = myatoi(line);
		printf("you typed %d\n", n);
		}

	return 0;
}


This first implementation of myatoi works, but only if the string contains digits and only digits. If the string contains any character other than a digit, the expression str[i] - '0' will result in a meaningless digit value. Therefore, a better implementation of myatoi would ensure that it only attempted to convert digits.

There are in fact several situations under which the string might contain characters other than digits. It might contain some spaces before the number to be converted (e.g. " 123"), or the number to be converted might begin with a minus sign. In any case, the caller might carelessly pass a string which contained some non-digit characters (perhaps at the end, following some digit characters). We will next look at an improved version of myatoi which allows for these possibilities.

The standard library contains several functions which test for various classes of characters. The isspace function returns nonzero (i.e. ``true'') for a whitespace character (e.g. space or tab). Similarly, the isdigit function returns nonzero (i.e. ``true'') for a digit character. These functions are declared in the header <ctype.h>. Here is an improved version of myatoi which uses isspace to skip leading whitespace and isdigit to ensure that it attempts to convert only digits. It also checks for a leading minus sign (but after checking for leading whitespace).
#include <ctype.h>

int myatoi(char str[])
{
	int retval = 0;
	int i = 0;
	int negflag = 0;
	
	while(isspace(str[i]))	/* skip leading whitespace */
		i++;
	
	if(str[i] == '-')	/* look for - sign */
		{
		negflag = 1;
		i++;
		}

	while(str[i] != '\0' && isdigit(str[i]))
		{
		int digit = str[i] - '0';
		retval = 10 * retval + digit;
		i++;
		}

	if(negflag)
		retval = -retval;

	return retval;
}
Notice that negflag is an example of a ``Boolean'' variable--we store a 0 or 1 in it to represent a ``false'' or ``true'' condition which we then test for later. (If we find a minus sign, we find it before we scan and convert the digits, but it's easier to make use of the fact that we saw a minus sign--to make use of it, that is, by actually negating the number we converted--after we convert it.)

Since the improved function scans and converts digits only as long as they are digits (as confirmed by isdigit), this version will quietly ignore any trailing non-digits. That is, atoi("123xyz") will simply return 123. (This is exactly the way the standard atoi function behaves.) Furthermore, calling myatoi (or atoi) with a string containing no digits at all, such as "abcxyz", will quietly return 0.

Exercise 6. Write a rudimentary checkbook balancing program.

#include <stdio.h>
#include <stdlib.h>	/* for atof() */

#define MAXLINE 100

extern int getline(char [], int);

int main()
{
	double balance = 0.0;
	char line1[MAXLINE], line2[MAXLINE];

	while(getline(line1, MAXLINE) > 0)
		{
		getline(line2, MAXLINE);

		if(strcmp(line1, "deposit") == 0)
			balance += atof(line2);
		else if(strcmp(line1, "check") == 0)
			balance -= atof(line2);
		else	{
			printf("bad data line: not \"check\" or \"deposit\"\n");
			continue;
			}

		printf("balance: %.2f\n", balance);
		}
	
	return 0;
}

Reading the key word and the amount from the same line would be surprisingly difficult, using only the tools we have in hand so far. We'll see a clean way of doing it in a week or two.

Exercise 7. Rewrite the ``compass'' code to use strcpy and strcat.

char word[20];

if(y > 0)
	strcpy(word, "north");
else if(y < 0)
	strcpy(word, "south");
else	strcpy(word, "");		/* empty string */

if(x > 0)
	strcat(word, "east");
else if(x < 0)
	strcat(word, "west");
else	strcat(word, "");		/* empty string */

printf("%s\n", word);

Exercise 8. Write a program to read its input, one character at a time, and print each character and its decimal value.

#include <stdio.h>

int main()
{
	int c;

	while((c = getchar()) != EOF)
		printf("character %c has value %d\n", c, c);

	return 0;
}
You will notice that this program prints a funny line or two for each new line ('\n') in the input, because when the %c in the printf call finds itself printing a \n character that we've just read, it naturally prints a newline at that point.

Exercise 9. Write a program to read its input, one line at a time, and print each line backwards.

Here is one way of doing it, using only what we've seen so far:

#include <stdio.h>

extern int getline(char [], int);
extern int reverse(char [], int);

int main()
{
	char line[100];
	int len;

	while((len = getline(line, 100)) != EOF)
		{
		reverse(line, len);
		printf("%s\n", line);
		}

	return 0;
}

int reverse(char string[], int len)
{
	int i;
	char tmp;
	for(i = 0; i < len / 2; i = i + 1)
		{
		tmp = string[i];
		string[i] = string[len - i - 1];
		string[len - i - 1] = tmp;
		}
	return 0;
}
In practice, it would be a nuisance to have to pass the length of the string to the reverse function. Strings in C are always terminated by the ``zero'' or ``nul'' character, represented by \0. Therefore, reverse (or any piece of code) can always compute the length of a string, either by searching for the \0, or by calling the library function strlen, which computes the length of a string by searching for the \0. Here is how the program might look if the reverse function did not require that the length of the string be passed in:
#include <stdio.h>
#include <string.h>

extern int getline(char [], int);
extern int reverse(char []);

int main()
{
	char line[100];

	while(getline(line, 100) != EOF)
		{
		reverse(line);
		printf("%s\n", line);
		}

	return 0;
}

int reverse(char string[])
{
	int len = strlen(string);
	int i;
	char tmp;
	for(i = 0; i < len / 2; i = i + 1)
		{
		tmp = string[i];
		string[i] = string[len - i - 1];
		string[len - i - 1] = tmp;
		}
	return 0;
}


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