Systematic and Structured Programming, Part 2
Step Three - Locating functions and Creating Specifications
Once again, step two was not difficult. In fact, between writing steps one and two, you probably still don't have a whole 20 minutes invested in this process. That's just one of the benefits here.
Once again (again), you will also find that step three is just an expansion of step two, although you will be taking some new actions here. Mostly however, you will just be filling in areas that are guided by what you wrote in your previous steps.
If you are working through this with the web page, you should create a new QuadProg_StepThree.c file and copy your QuadProg_StepTwo.c code into it so that you don't overwrite your second step. You will be reminded again to make this change at the end of this web page.
Now, you must work your way through the program from top to bottom following the iterative refinement process. This time, you will be identifying the need for, and the use of, functions in your program.
Start here
Initialize the program
// initialize program
// initialize variables
// show title
There is still very little to do here, but we will create a function to conduct the print title operation
You have specified that you want to print a title. This is not terribly complicated but it's a place to start
Consider the requirements you have specified related to printing the title:
- what do they have in common?
- the title will always be printed the same way
- what is different about each of them?
- in this case, nothing. Again the title will always be printed the same way
Taking a minute or two to consider this information will help you figure out how to design the necessary function
- it will print the title (process)
- it doesn't need any information to do its job (function parameter input)
- it will not conduct any processing and therefore will not return anything to the calling function (function return output)
- it will not capture any input from any devices but it will send output to the device: Monitor
- it will use a Console IO Utilities to conduct the printing (dependencies)
So now, you create a specification for your function up above the main function in the // function prototypes area that looks like the following:
/*
Name: displayTitle
Process: displays the program title on the monitor
Function Input/Parameters: none
Function Output/Parameters: none
Function Output/Returned: none
Device Input/Keyboard: none
Device Output/Monitor: title displayed
Dependencies: Console IO Utilities tools
*/
This is also the point where you can create the function prototype and the stub function for later use. Creating the prototype will tell the compiler that the function exists prior to its use in the main function, and creating the stub function will support correct compiling, and will later be there when it comes time for you to conduct steps five and six.
If you recall from Chapter 0, the whole structure of a program source file is as follows:
// headers or include files
// global constants
// function prototypes
// the main program
// suppporting function implementations
The function prototype is placed immediately under the specification block and in the function prototypes area, and the stub function is placed under the supporting function implementations as shown here
// headers or include files
// global constants
// function prototypes
/*
Name: displayTitle
Process: displays the program title on the monitor
Function Input/Parameters: none
Function Output/Parameters: none
Function Output/Returned: none
Device Input/Keyboard: none
Device Output/Monitor: title displayed
Dependencies: Console IO Utilities tools
*/
void displayTitle();
// the main program
// suppporting function implementations
void displayTitle()
{
}
Note that the displayTitle prototype has a semicolon after it, and the stub function down below has curly braces (and no semicolon).
Make sure you don't have any errors after writing this code. Next up is the input operation to get the coefficients.
Get the coefficients from the user
// get coefficients from user
// get coefficient A
// get coefficient B
// get coefficient C
You have specified that you want to get the three coefficients from the user. This will certainly require one or more functions in order to keep the program modular (and to keep your main function from getting more complicated)
Consider the requirements you have specified related to getting the coefficients from the user:
- what do they have in common?
- each one of them will be prompted for a coefficient input
- each one of them will return an integer result (i.e., one of the coefficients)
- what is different about each of them?
- the prompt will be slightly different. For example the first prompt will ask for coefficient A, the second for coefficient B, and so on
- otherwise, they do the same thing by asking the user for a coefficient and acquiring it; that is a hint that a single function can be used to solve this problem
Taking a minute or two to consider this information will help you figure out how to design the necessary function
- it will prompt the user for the specified coefficient letter, it will acquire the coefficient from the user, and then it will return the integer coefficient to the calling function (i.e., the main function in this case) (process)
- it will need to know which coefficient is being prompted for (function parameter input)
- it will return an integer value that is the requested coefficient (function return output); at this point, it will not be necessary to use the function parameter output
- it will capture input from the device: Keyboard, and it will send the prompt message to the device: Monitor
- it will use Console IO Utility tools to conduct the user interaction (dependencies)
So now, you create a specification and a prototype up in the function prototypes area alphabetically after displayTitle that looks like the following:
/*
Name: promptForCoefficient
Process: prompt user for appropriate coefficient,
get coefficient, return coefficient
Function Input/Parameters: coefficient letter (char)
Function Output/Parameters: none
Function Output/Returned: acquired coefficient (int)
Device Input/Keyboard: user input
Device Output/Monitor: prompt displayed
Dependencies: Console IO Class tools
*/
int promptForCoefficient( char coefLetter );
And in the supporting function implementations area, alphabetically after displayTitle
int promptForCoefficient( char coefLetter )
{
return 0; // temporary stub return
}
Two notes about this:
- Note that it is required to place the return 0; statement in the function. If this is not provided, the compiler will complain that the function is not returning the data it had promised to return
- You should always add the "// temporary stub return" after this line so you will remember to change the return statement as needed later on when you are implementing the code
Again, make sure there are no errors or warnings as you compile during and after writing these lines.
Now you will write the name of the function in your program as shown. You can see how this would look right at this point here. Notice the re-use of the function; this is already evidence of good quality programming.
// get coefficients from user
// get coefficient A
// function: promptForCoefficient
// get coefficient B
// function: promptForCoefficient
// get coefficient C
// function: promptForCoefficient
Process the quadratic roots
// Process the quadratic roots
// calculate the discriminant
// calculate the square root of the discriminant
// calculate the denominator
// Calculate the roots
// calculate root one
// calculate root two
There are several actions to be taken here, but that just provides an opportunity to practice with simple functions
For the first operation ("calculate the discriminant"), you have specified that you want to calculate the discriminant (i.e., the calculations under the radical). As before, this will require a function in order to keep the program modular
Consider the requirements you have specified related to calculating the discriminant for every combination of coefficients the user provides:
- what do they have in common?
- each time the function is called, it will have to calculate b2 - 4ac
- what is different about each of them?
- the coefficients A, B, and C will likely be different every time the function is called
- otherwise, the function does the same thing by acquiring the three coefficients and calculating the discriminant
Taking a minute or two to consider this information will help you figure out how to design the necessary function
- it will accept the three coefficients, calculate the discriminant, and then return the result to the calling function (i.e., the main function in this case) (process)
- it will need to know all three coefficients (function parameter input)
- it will return an integer value that is the discriminant (function return output)
- it will only be conducting basic math operations, so it does not need to use any other functions (dependencies)
So now, you create a specification and the stub for your function up in the function prototypes area alphabetically before the getCoefficient function that looks like the following:
/*
Name: calcDiscriminant
Process: calculate discriminant with three coefficients
and return
Function Input/Parameters: three coefficients (int)
Function Output/Parameters: none
Function Output/Returned: calculated discriminant (int)
Device Input: none
Device Output: none
Dependencies: none
*/
int calcDiscriminant( int cofA, int cofB, int cofC );
And again, place the stub function down below alphabetically before the getCoefficient function, as shown here
int calcDiscriminant( int cofA, int cofB, int cofC )
{
return 0; // temporary stub return
}
For the next operation ("calculate the square root of the discriminant"), the square root of the discriminant must be found. You could write a square root calculation function, but for purposes of this activity, it is much easier to use the library function sqrt. So, since you will be using the math.h library function, you will not have to do an analysis of the problem, and you will not have to create a function. The function: sqrt will be placed under this step two statement.
For the next operation ("calculate the denominator"), you have specified that you want to calculate the denominator of the quadratic equation. As before, this will require a function in order to keep the program modular
Consider the requirements you have specified related to calculating the denominator for every coefficient A the user provides:
- what do they have in common?
- each time the function is called, it will have to calculate 2 * A
- what is different about each of them?
- the coefficient A will likely be different every time the function is called
- otherwise, the function does the same thing by acquiring the coefficient and calculating the denominator
Taking a minute or two to consider this information will help you figure out how to design the necessary function
- it will accept the A coefficient, calculate the denominator, and then return the result to the calling function (i.e., the main function in this case) (process)
- it will need to know the coefficient A (function parameter input)
- it will return an integer value that is the denominator (function return output)
- it will only be conducting basic math operations, so it does not need to use any other functions (dependencies)
So now, you create a specification for your function up in the function prototype area alphabetically prior to calcDiscriminant that looks like the following:
/*
Name: calcDenominator
Process: calculate denominator with coefficient A
and return
Function Input/Parameters: coefficient A (int)
Function Output/Parameters: none
Function Output/Returned: calculated denominator (int)
Device Input: none
Device Output: none
Dependencies: none
*/
int calcDenominator( int coef_A );
And the stub function in the supporting function implementations down below:
int calcDenominator( int coef_A )
{
return 0; // temporary stub return
}
Finally, for the last root processing operations ("calculate root one" and "calculate root two"), you have specified that you want to calculate the two roots of the equation using the solution components you have already generated (i.e., the square root of the discriminant and the denominator). As usual, this will require a function in order to keep the program modular.
Consider the requirements you have specified related to calculating the root for every set of data the user provides:
- what do they have in common?
- each time the function is called, it will be provided with the coefficient B, the square root of the discriminant, and the denominator, all of which are components in the quadratic equation
- each time the function is called, it will have to calculate negative b + (either the positive or negative) discriminant, all divided by the denominator
- what is different about each of them?
- the square root of the discriminant must be positive to calculate one root, and negative to calculate the other root; that can be resolved by simply making the square root of the discriminant positive or negative when it is passed to the function as a parameter
- otherwise, the function does the same thing by acquiring the three components and calculating each individual root
Taking a minute or two to consider this information will help you figure out how to design the necessary function
- it will accept the three specified components, calculate the root, and then return the result to the calling function (i.e., the main function in this case) (process)
- it will need to know the coefficient B, the square root of the discriminant (passed either as a positive or negative value), and the denominator (function parameter input)
- it will return a double value that is one of the root solutions (function return output)
- it will only be conducting basic math operations, so it does not need to use any other functions (dependencies)
So now, you create a specification for your function up in the function prototypes area alphabetically after the calcDiscriminant function that looks like the following:
/*
Name: calcRoot
Process: calculate single root solution with previously
processed equation components, and return
Function Input/Parameters: coefficient B and the denominator (int),
and the square root
of the discriminant (double)
Function Output/Parameters: none
Function Output/Returned: calculated single root (double)
Device Input: none
Device Output: none
Dependencies: none
*/
double calcRoot( int coef_B, int denominator, double discSqrt );
And once again, placing the stub function down in the supporting function implementations area:
double calcRoot( int coef_B, int denominator, double discSqrt )
{
return 0; // temporary stub return
}
Now that you have specified (i.e., created the specifications for) the functions you wish to use, you may fill in the appropriate areas in the main part of your program, as shown here
// Process the quadratic roots
// calculate the discriminant
// function: calcDiscriminant
// calculate the square root of the discriminant
// function: sqrt
// calculate the denominator
// function: calcDenominator
// calculate the roots
// calculate root one
// function: calcRoot
// calculate root two
// function: calcRoot
Now that the processing has been completed, you can move on to the display part of the program.
Display the roots
// display roots
// display user input values
// display root one
// display root two
The actions are also pretty easy for the output display, but you can again take advantage of the characteristics of functions if you will take the time to think it through. Consider the "what's in common/what's different" questions for these two more processes
There are going to be a variety of functions needed to display the user input. This is a reason to abstract the operation (i.e., hide the details) and place the various print statements in a function.
Consider the requirements you have specified related to displaying the user input:
- what do they have in common?
- each time the function is called, it will be provided with the three user input values
- what is different about each of them?
- the input values will be different
Taking a minute or two to consider this information will help you figure out how to design the necessary function
- it will accept the input values and display them (process)
- it will accept the input values (function parameter input)
- it will not return anything to the calling function, but it will display the input values on the screen (device output)
- it will be conducting output operations using Console IO Utility tools (dependencies)
So now, you create a specification for your function up in the function prototype area alphabetically after the calcDiscriminant function that looks like the following:
/*
Name: displayUserInput
Process: display user input values prior to displaying roots
Function Input/Parameters: three user input values (int)
Function Output/Parameters: none
Function Output/Returned: none
Device Input: none
Device Output/Monitor: user input values
Dependencies: Console IO Utility tools
*/
void displayUserInput( int coefA, int coefB, int coefC );
Place the stub function down below, alphabetically
void displayUserInput( int coefA, int coefB, int coefC )
{
}
There will also be more operations to display the roots. However, one function can be used for displaying each root individually.
Consider the requirements you have specified related to displaying one of the roots for every set of data the user provides:
- what do they have in common?
- each time the function is called, it will be provided with a root solution, and the number of the root solution (either root 1 or root 2)
- each time the function is called, it will have to display the root number (1 or 2) and then the root solution related to that number
- what is different about each of them?
- the input values will be different
- the root number will be different and the root solution will be different
- otherwise, the function does the same thing by acquiring the root number and the root, and displaying useful information to the user
Taking a minute or two to consider this information will help you figure out how to design the necessary function
- it will accept the root number and the root, and it will display the root with its number on the screen (process)
- it will need to know the root number and the individual root solution (function parameter input)
- it will not return anything to the calling function, but it will display a result on the screen (device output)
- it will be conducting output operations using Console IO Utility tools (dependencies)
So now, you create a specification for this function up in the function prototype area alphabetically after the calcDiscriminant function (but before the displayUserInput function) that looks like the following:
/*
Name: displayRoot
Process: display root number and root solution
Function Input/Parameters: root number (1 or 2) (int),
and root solution (double)
Function Output/Parameters: none
Function Output/Returned: none
Device Input: none
Device Output/Monitor: root number and root solution
Dependencies: Console IO Utility tools
*/
void displayRoot( int rootNum, double rootValue );
And once again, the stub function goes down in the supporting function implementation area
void displayRoot( int rootNum, double rootValue )
{
}
Note in this case, like the displayTitle, these functions display an output to the device (i.e., the monitor) but do not return any processed data back to the calling function (i.e., the main function). Thus, there is no return statement for either displayUserInput or displayRoot.
With the display function specified, you can place it in the appropriate place in your code
// display roots
// display user input values
// function: displayUserInput
// display root one
// function: displayRoot
// display root two
// function: displayRoot
End the program
// end program
// display program end
You will actually need two more functions to show the end of the program, but like the square root function, these have also been written for you. You will simply be using the printString and printEndline functions for this part.
// end program
// display program end
// function: printString, printEndline
Wrapping Up Specifications and Stub Functions
The stub creation part of step three is mostly a management process so that your program will continue to compile correctly even as you move into step four. It is appropriate to do it in this step because this is where you are specifying and locating the functions you will use.
Having said that, creating these code components is as easy as following the instructions you gave yourself. It never needs to be any more difficult than this
Some more notes:
- This third step takes a little longer to write -- not that long -- but a little longer. However, if you consider that you have now created the bulk of the program and the remainder of your task is to just fill in the blanks you have now created, you have moved way ahead
- If you have been following this process carefully, you will notice that there are no parts in it where you fall back in your chair or on the floor and cry, "Why me . . . why me! This is sooooooooooo hard!" That does not seem to be necessary for this process. You just take one step at a time and you design and develop a program. Yes, if it is a non-trivial program, it will be complicated when you finish it; but no, it does not have to be complicated while you are developing it
- In order to conduct this part of the process, you must remember to create a new QuadProg_StepThree.c file and copy your QuadProg_StepTwo.c code into it before you make any changes. You want to keep your step two program exactly as it was so that if there are questions in the future about your overall strategy, you can go back and review the code
- Remember as you type in your function specifications, you must still make sure the program compiles without errors or warnings; in other words, keep compiling every two or three lines. This continues to be trivial because you are not adding much code. However, it remains important that no unknown issues pop up right now, and besides, it is a great habit to get into (that you will be using quite a bit in the next step)
This step -- in this page -- may seem long but it is mostly because code is typed and re-typed quite a bit. The actual step three takes no more time than the typing you need to do. Again, much of what you did in this step was just following instructions from the previous steps and much of what you will do in the succeeding steps will be following the instructions you created here.
To see the step three process in action, watch this video; then develop your step three code with or without the video as needed.