You can see an index of all the posts in this series: go to index.
If you want to review where I got to by this point, here are the starter files from the end of the previous post: more on decisions and functions.
In the previous post I made improvements to the code that reads and validates the command line. Now that I have the number of phrases the learner wants to see, it’s time to do something with it.
I changed my code so it looked like this (changes highlighted):
/******* * Buzzword * * A program to create amusing fake jargon *******/ #include <stdio.h> #include <stdlib.h> #include <time.h> #include <errno.h> // Function declarations void displayUsageAndExit(); int main(int argc, const char * argv[]) { int numberOfPhrases = 0; if (argc == 1) { // There was no argument so default to one phrase numberOfPhrases = 1; } else if(argc != 2) { // An incorrect number of arguments was given, so quit with usage message displayUsageAndExit(); } else { // A single argument was given, so attempt to convert it to an integer errno = 0; char * inputStr; long inputNum = strtol(argv[1], &inputStr, 10); if(errno == ERANGE || *inputStr != '\0' || inputNum < 1 || inputNum > 99) { // The argument could not be interpreted as an integer in the required range, so quit with usage message displayUsageAndExit(); } numberOfPhrases = (int) inputNum; } // Temporary statement to check validation code by showing the number of phrase requested // printf("\nThe user asked for %i phrase(s)\n", numberOfPhrases); // Display the instructions printf("Buzzword Generator\nBased on a program from Creative Computing, Morristown, New Jersey\n\n\nThis program prints highly acceptable phrases in 'educator-speak' that you can work into reports and speeches. Whenever a question mark is printed, type a 'Y' for another phrase or 'N' to quit.\n\n\nHere's the first phrase:\n"); // Create three lists of words const char * adjectives1[] = {"ability", "basal", "behavioral", "child-centered", "differentiated", "discovery", "flexible", "heterogeneous", "homogenous", "manipulative", "modular", "tavistock", "individualized" }; const char * adjectives2[] = {"learning", "evaluative", "objective", "cognitive", "enrichment", "scheduling", "humanistic", "integrated", "non-graded", "training", "vertical age", "motivational", "creative"}; const char * nouns[] = {"grouping", "modification", "accountability", "process", "core curriculum", "algorithm", "performance", "reinforcement", "open classroom", "resource", "structure", "facility", "environment"}; // Seed the random number generator srand((unsigned int)time(NULL)); // Display the number of randomly generated phrases requested by the user int i = 0; while (i < numberOfPhrases) { printf("\n%s %s %s\n\n", adjectives1[rand() % 13], adjectives2[rand() % 13], nouns[rand() % 13]); i++; } return EXIT_SUCCESS; } void displayUsageAndExit() { printf("\nUsage: BUZZWORD number_of_phrases\nWhere number of phrases is a whole number from 1 to 99"); exit(EXIT_FAILURE); }
The first thing to note is that I’ve commented out the printf statement on line 40 that displayed the number of phrases requested. This was useful while I was still testing and developing my code to read and validate the command line argument. Now that I’m satisfied that code is working, I don’t need it.
You’ve seen how I can control whether a block of code gets executed by using if and else commands. Sometimes I need to execute a block of code multiple times. This is exactly the situation I have here. I want to execute line 56 as many times as it takes to display the number of phrases requested by the user. One way to do that is with a while command. While works in a similar way to if, in that it has a condition in parentheses followed by a body between braces. However, whereas if will execute the statements in its body once if the condition is true, while will execute the code in its body repeatedly while the condition is true.
Let’s assume that the user has requested two phrases and follow the code through step by step to see what happens. In line 54 I declare and initialise a counter called i. The counter will keep track of the number of phrases I’ve displayed. In line 55 the while command checks to see if i is less than numberOfPhrases. It is, because i is currently 0 and numberOfPhrases is 2. So the body (lines 56 and 57) is executed.
The first line of this code displays a randomly generated phrase – I introduced this line in previous posts. The second line introduces a new operator, the increment operator ++. The statement i++ is equivalent to i = i + 1. The reason there is a special operator for it is that adding 1 to a variable is something programmers end up doing a lot in code. There’s also an equivalent decrement operator, —, which subtracts 1 from a variable. So this second line is adding 1 to my counter because I’ve just displayed a phrase.
One important thing to note about the increment and decrement operators is that they can work in two different ways, with the operator placed either before the variable, e.g. ++i, or after the variable, e.g. i++. These both do the same thing if they are used on their own in a statement, the difference comes when they are used in an expression and/or an assignment. When the prefix form is used, ++i, the value of i used in the expression or assignment will be its value after it has been incremented. In other words, this code:
int i = 1; int result = ++i - 2; printf("result is %i and i is %i\n", result, i);
would display “result is 0 and i is 2”.
If I use the postfix form, i++, the value of i used in the expression or assignment will be its value before it is incremented. So, this code:
int i = 1; int result = i++ - 2; printf("result is %i and i is %i\n", result, i);
would display “result is -1 and i is 2”.
OK, back to my Buzzword code. Now we reach the end of the body, marked by the closing brace on line 58. Instead of continuing with the next bit of code outside the braces, as would happen with an if command, control is passed back to the while command on line 55, which once again checks to see if i < numberOfPhrases. It is, because i is now 1 and numberOfPhrases is 2.
So the body is executed once more. A second phrase is printed and i is incremented, so it now equals 2.
Once again we reach the end of the body and go back to the while command on line 55. This time, when the condition is checked, i is not less than numberOfPhrases, because they both now equal 2. So now the body is not executed and control passes to the statement following the closing brace, which in this case is the return statement on line 60
I saved and compiled this code and ran it with a single digit command line argument, 5. This is what I saw:
Counting the phrases, I was satisfied that had exactly five, no more, no less.
Here’s another example of a while loop:
int count = 0; int keepGoing = 1; while (keepGoing = 1) { count++; if (count == 5) { keepGoing = 0; } } printf("count is %i", count);
Take a close look at this loop and see if you can work out what number will get displayed by the printf command in line 11.
If you said 5, then you’re starting to get the hang of loops, but you missed something that would prevent this from happening. If you said the number will never get displayed, then my hat off to you for your powers of observation and deduction.
You’ll remember from a previous post that a common programming error is to use the assignment operator, =, when I meant to use the equality operator, ==. You’ll also recall that when I use the assignment operator in a condition, it always evaluates as the value of the assignment. If you look closely at line 4 you’ll see that’s exactly the mistake that’s been made here. Each time the condition on line 4 is evaluated, rather than being compared to 1, keepGoing will be set to 1. Since an assignment always evaluates as the value of the assignment, the condition will always evaluate as 1. You’ll recall that C interprets any non-zero number as true, so this loop will never end and the printf command will never be reached.
This is called an infinite loop. If I ever have a program seem to hang on me and sit there doing nothing, one of the first things to check is that I haven’t made this sort of error in my loops.
Let me review the parts of my loop in Buzzword. I have:
- a counter called i, which I declare and initialise on line 54
- a condition, on line 55, that is evaluated at the start of each loop to determine if the loop should continue
- a statement on line 57 that updates the counter.
Now, it so happens that the need for a loop that includes these three things occurs so often in C programs that there’s another command, called a for loop, which is dedicated to constructing loops of this nature. I’ll update my code to use a for loop instead of a while loop. I updated lines 54 to 58 in the current code to the following:
// Display the number of randomly generated phrases requested by the user for (int i = 0; i < numberOfPhrases; i++) { printf("\n%s %s %s\n\n", adjectives1[rand() % 13], adjectives2[rand() % 13], nouns[rand() % 13]); }
You can see that the for loop is a lot more compact than the equivalent while loop. That’s because all three of the components I identified are included within the parentheses following for, and separated with semicolons:
Note that, like a while loop, the for loop executes the first of these parts, the initialisation statement, once before the start of the loop. The second part, the condition, is checked each time through the loop before the body is executed. The final statement is executed after the body has been executed. You should also note that there is no semicolon after this third part.
When I tried building and running the program again I got exactly the same number of phrases as I did with the while loop.
Notice that with both forms of loop my counter started at zero and I incremented it at the end of the loop. What that means is that, once the counter is equivalent to numberOfPhrases, I have already displayed enough phrases. That’s why I’m checking that the counter is less than numberOfPhrases: (i < numberOfPhrases), not less than or equal to numberOfPhrases: (i <= numberOfPhrases). For that to work, I’d have had to change the declaration and initialisation of i to int i = 1. It’s easy to mismatch my condition and the starting value of my counter, which will mean my loop will execute either one time more than it should or it will stop one cycle early. This is known as an off by one error. It’s another common error to check for if my code isn’t behaving as I expect.
At this point I want to address something that might have been bothering you. In a previous post, I stressed the importance of giving meaningful names to variables, and yet here I am calling a variable i. Well in this case it’s a valid exception. It’s traditional to use i for counters in loops. These types of variable are sometimes referred to as index variables, which is where the i comes in. I can safely use i as a variable name in this way, as experienced programmers will understand, from the context, what it means.
This is all great, and I’ve had fun extracting information from the command line, but so far I am not living up to the promise of the original program. In the original, the user could decide after each phrase if they wanted another phrase or not. I’ll fix that in the next post.
In the meantime, here’s another challenge for me to ponder. There’s yet another type of loop in C, called the do … while loop. It looks like this:
do {
.
.
.
} while (condition);
Here’s how it works. Everything in the braces is executed at least once. At the end of each execution, the condition is tested and, if it’s true, the loop is repeated. One thing to note about this type of loop is that, because the condition comes at the end, not the beginning, it’s terminated with a semi-colon.
My challenge is to rewrite the program to use a do … while loop. The solution will be shown in the next post.
Be First to Comment