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: command line arguments and decisions.
In the previous post in this series, I added code to read and validate command line arguments. I also ended by considering a couple of challenges. The first of these was to change the validation code so that, if the program is run with no arguments, it should default to showing one phrase.
Here’s my solution (the changes I’ve made are highlighted):
/******* * Buzzword * * A program to create amusing fake jargon *******/ #include <stdio.h> #include <stdlib.h> #include <time.h> #include <errno.h> #include <limits.h> 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 printf("\nUsage: BUZZWORD number_of_phrases\nWhere number of phrases is a whole number from 1 to %i\n", INT_MAX); return EXIT_FAILURE; } 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 > INT_MAX) { // The argument could not be interpreted as an integer in the required range, so quit with usage message printf("\nUsage: BUZZWORD number_of_phrases\nWhere number of phrases is a whole number from 1 to %i\n", INT_MAX); return EXIT_FAILURE; } numberOfPhrases = (int) inputNum; } // Temporary statement to check validation code by showing the number of phrases requested printf("\nThe user asked for %i phrase(s)\n", numberOfPhrases); // Display the instructions printf("Buzzword Generator\nBased on a programme from Creative Computing, Morristown, New Jersey\n\n\nThis programme 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 and display a randomly generated phrase srand((unsigned int)time(NULL)); printf("\n%s %s %s\n\n", adjectives1[rand() % 13], adjectives2[rand() % 13], nouns[rand() % 13]); return EXIT_SUCCESS; }
Notice that, now my program is starting to get a bit larger, I’ve added comments to explain what each section does. If I am intending to share my code with other programmers, or have other programmers collaborate with me on a project, commenting my code is essential. But, even if that isn’t the case, it’s still a good habit to get into. Although I might not need those comments now, when I come back to work on an old piece of code I haven’t looked at for a while, I’ll be glad I made the effort as it will save me a lot of head-scratching and frustration trying to work out why I did things in a certain way.
OK, let’s have a look at what’s going on here. In line 17 I’ve added a new if command that checks to see if argc is equal to 1. Remember that there is always at least one command line argument, which is the path and filename that was entered to run the program. So if argc == 1 is true, that means the user didn’t enter any additional arguments. If this is the case, I simply set the numberOfPhrases to the default value of 1 on line 19.
On line 20 you can see the else command that I’ve used before, but it’s immediately followed by another if statement. The condition in the second if will only get checked if the condition for the first if is false. You can chain any number of if statements together this way:
So you can see that the logic for my program is:
- check if no arguments have been given (line 17) and use a default value if so (line 19)
- if some arguments have been given, check that the right number of arguments have been given (line 20) and quit if not (lines 22 and 23)
- If neither of those things is true, then that means exactly one argument has been given, so get it and attempt to convert it into an number (line 28), quitting if it can’t be converted or is out of range (lines 32 and 33).
OK I’ll check if all this works. I saved and compiled the program, then ran it without any command line arguments. I saw that it correctly defaulted to 1 phrase.
Having run it a few more times with various arguments, I satisfied myself that it did still work when an argument was supplied and that all the error checking still functioned as it should.
The other part of this challenge was to limit the number of phrases that could be requested to a reasonable range, e.g. 1 to 99. The lower limit 1, is already taken care of, but my program will quite happily accept a request for hundreds of thousands of phrases, which I don’t want.
Here’s my solution (again, the changes I’ve made from the version above are highlighted):
/******* * Buzzword * * A program to create amusing fake jargon *******/ #include <stdio.h> #include <stdlib.h> #include <time.h> #include <errno.h> // #include <limits.h> 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 printf("\nUsage: BUZZWORD number_of_phrases\nWhere number of phrases is a whole number from 1 to 99\n"); return EXIT_FAILURE; } 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 printf("\nUsage: BUZZWORD number_of_phrases\nWhere number of phrases is a whole number from 1 to 99\n"); return EXIT_FAILURE; } 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 programme from Creative Computing, Morristown, New Jersey\n\n\nThis programme 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 and display a randomly generated phrase srand((unsigned int)time(NULL)); printf("\n%s %s %s\n\n", adjectives1[rand() % 13], adjectives2[rand() % 13], nouns[rand() % 13]); return EXIT_SUCCESS; }
This one was a bit easier. I just had to replace INT_MAX in line 30 with 99. Because I’ve changed the limit, I also had to update the usage messages in lines 22 and 32. Note that, now I’m no longer using INT_MAX, I don’t need to include limits.h, so line 11 is commented out.
I mentioned in the last post that there were two additional comparison operators, <= and >=. You might have guessed that these mean less than or equal to and greater than or equal to respectively. So I could, for example, have replaced this conditon:
inputNum < 1
with:
inputNum <= 0
and likewise:
inputNum > 99
with
inputNum >= 100
Generally I’d want to use the one that’s going to make most sense to a reader of my code given the context.
While I’m looking at this if statement, I should revisit the logical operators. You saw in the previous post that the double vertical bars, ||, are the logical OR operator. There’s also a logical AND operator, &&. They work as follows.
- The OR operator, ||, will return true if either the left operand or right operand is true (or if both operands are true) and false if the left operand and right operand are both false.
- The AND operator, &&, will return true only if both the left operand and right operand are true, and will return false for all other cases.
There’s also a third logical operator called NOT, !, that takes a single operand and will change its value from true to false, or from false to true.
It’s common to express logical operations like this in a truth table, which shows the output for all possible combinations of input. Here are the truth tables for these three operators:
a || b (a OR b)
a | b | output |
false | false | false |
false | true | true |
true | false | true |
true | true | true |
a && b (a AND b)
a | b | output |
false | false | false |
false | true | false |
true | false | false |
true | true | true |
!a (NOT a)
a | output |
false | true |
true | false |
An important thing to note about logical operators is that C will be efficient about the way it checks them. So in my statement on line 30, if it discovers that errno == ERANGE is true, it won’t bother to check the other three conditions since it knows that their results are no longer going to make any difference to the result of the whole expression. Can you see why?
An OR operation is true if either of the operands is true, so as soon as any of the conditions in this expression is true, the whole expression must be true. You’d get a similar situation if the expression was something like (errno == ERANGE && inputNum <1). In this situation, if errno==ERANGE turned out to be false, C wouldn’t bother checking to see if numberOfPhrases was less then 1 because the result is irrelevant at that point – in an AND operation, if either operand is false the whole expression must evaluate to false. This technique is known as short-circuit evaluation.
it’s a good idea to test my program with:
- No arguments
- Too many arguments
- A single argument that’s a word rather than a number
- A single argument that’s a number immediately followed by some non-numeric characters (without any space between)
- A single argument that’s too small (0 or negative)
- A single argument that’s too big (100 or higher)
- A single argument that’s within the correct range (1 to 99)
I checked that I got the results I expected for each of these tests. You’ll notice that I didn’t test just the new checks for range, but all of the previous validation checks too. This is a practice known as regression testing. It checks that changes I’ve made to my program haven’t introduced new errors or reintroduced old errors into code that was previously working correctly. It’s sensible to get into the habit of doing this whenever I make changes to my code.
I posed a second challenge at the end of the previous post, which was to consider that I have two blocks of code, lines 22 – 23 and lines 32 – 33 that are identical. I wanted to think about whether this was an acceptable situation.
If you think it is acceptable, consider this. To date, I’ve made two changes to my program that have affected those usage statements: firstly to check for a large range, and subsequently to limit the range from 1 to 99. For each of those changes I’ve had to make exactly the same change to my usage statement in two different parts of the program. That’s also going to be the case for any further changes I make to it. If I forget to make the change in one place, I’ve created a poor user experience. In more extreme cases, not updating duplicate sections of code correctly could introduce errors.
The solution to this is to avoid the situation where I have two or more sections of code that perform an identical function. So what can I do about this? One solution is to rewrite the code in my main function so that the usage message only has to appear in one place. Changing the structure of code so that it results in the same output but does it in a more efficient way is known as refactoring. It’s something I’ll revisit in future posts.
Rather than restructure my main function, I’m going to take another approach, which is to move the duplicated code into its own function. I can then call that function whenever I need that code to run. (NB there is actually a third way round this particular problem, which is to use something called macros – this is an advanced topic that I’ll consider in a future post).
The new function will simply display a usage message and then end the program. I changed the program as follows (changes are highlighted – also note that I’ve now deleted the include statement for limits.h that I commented out in the last revision):
/******* * 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 programme from Creative Computing, Morristown, New Jersey\n\n\nThis programme 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 and display a randomly generated phrase srand((unsigned int)time(NULL)); printf("\n%s %s %s\n\n", adjectives1[rand() % 13], adjectives2[rand() % 13], nouns[rand() % 13]); 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 new function starts at line 57. I should give functions names that represent what they do, so a good name for this function is displayUsageAndExit. Note again that I am using camel case for my function names. I don’t have to do this, but whatever style I choose, I should be consistent with it. Note that, like variables, function names can contain only upper and lower case letters, digits and the underscore character. It’s not permitted to begin a function name with a digit.
Now this function doesn’t need any parameters, so the function name is followed by empty parentheses: displayUsageAndExit(). You’ll remember from my analysis of the main function that the function name is preceded with the return type. My new function won’t return any value, and to indicate this I use the keyword void.
The exit function on line 59 is new. This function is part of the standard library, stdlib. The exit function ends the current program. It accepts a single argument, which is a status code it sends the parent process. In other words, exit(EXIT_FAILURE) used in this function is performing a similar role to return EXIT_FAILURE in the main function. So why didn’t I just use return EXIT_FAILURE in this function as well?
You’ll recall that every C program has a main function and that main is the first function that is called when the program begins. In other words, main is called by the parent process – which in my case is the terminal app. When I use a return statement in main, it returns control to the parent process, effectively ending the program.
My function, displayUsageAndExit, is not called by the parent process, it’s called by main, in lines 24 and 33. So if I were to use a return statement in displayUsageAndExit it would return control to main. I’d have displayed my usage message, but my program would still be running – that’s not what I want at all. So, if I want to end the program in a function other than main, I need to use the exit function instead.
Before I move on, you might have noticed line 13. This looks identical to the start of my function definition, but it ends with a semicolon instead of a function body. C expects every function to be declared before it is used (in a similar way to how I declare variables before I use them). To declare a function I write the function return type, name and arguments in parentheses, followed by a semicolon. The function declaration can be anywhere in my file, but it must appear before the first call to that function. Practically though, I should group all my function declarations together near the beginning of the file.
OK, so at this point I ran my tests again, and found that the program behaves identically to the way it did before I created the new function.
In the next post, I’ll finally get round to doing something useful with that command line argument.
Be First to Comment