- 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 used to hold the string
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.
- 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
(in 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.
- 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.