CSCI 159 Lab 4 exercises
Lab 4 is a one week lab: due prior to the start of your lab on Nov. 4th/6th
There are still two halves (part half in basic.cpp and second half in lab4.cpp), but
there is no extended independent design section (i.e. no work on the stair counting
this time).
- a basic/warmup exercise (basic.cpp), worth 9 marks
- the main design/implementation exercise (lab4.cpp), worth 9 marks
- both parts are due before the start of lab 5 (Nov. 4/6).
Hopefully most people complete nearly the entire lab 4 in their scheduled lab section
on Oct. 28th/30th.
Here is the collection of new C++ syntax elements we'll be using
for lab4.
Follow our usual setup process
- Log in, open a browser, go to the lab4 page, open a terminal window:
(see lab 2 if you need a refresher on any of the steps)
- get lab4:
make -f make159 csci159/lab4
- Go into your lab4 directory and begin the edit/compile/test/submit routine:
for the first half of today's lab:
- to edit basic.cpp: pluma basic.cpp &
- to compile basic.cpp: make basicx
- to test basicx: ./basicx
- to submit: make submit
once we get to the second half of today's lab:
- to edit lab4.cpp: pluma lab4.cpp &
- to compile lab4.cpp to create lab4x: make lab4x
- to test lab4x: ./lab4x
- to submit: make submit
As with previous labs, you'll see that the two .cpp files are nearly empty to start with.
First half: the basics of loops and booleans (to be done in basic.cpp)
We'll be using the string and iostream libraries for basic.cpp, and writing three functions:
- skipThis (takes a string parameter and returns a char): the string parameter describes
a section (part one or part two), and the function
asks the user Y/N do they wish to skip this section. It gets their response (using a do while
loop to repeat until they answer one of YyNn) and returns true if they wish to skip, false otherwise.
- partOne (takes no parameters and returns a float): the function asks the user to
enter a number > 1, gets and checks their response (Using a while loop to repeat
until a valid value is given), and returns the eventual valid answer.
- partTwo (takes no parameters and has a void return type): this uses partOne to
get a number from the user then uses a for loop to calculate a series of better and better
estimates for the square root of that number.
We'll need several constants as well:
- a const char, 'Y', specifying which character represents yes
- a const char, 'N', specifying which character represents no
- an integer, 10, specifying how many estimates our root calculation will work through
- an integer, 80, specifying how many characters to discard on bad input
The main routine will:
- call skipThis("part one") and check the return value to see if the user wishes to
skip part one. If skipThis returns false then we do want to try partOne
here, so the test will look something like
if (!skipThis("part one")) {
If the user didn't skip then we want to call partOne and store/print the value
it returns (so we can check it did return the value correctly).
- do similarly for part two, but here the body of the if can simply call partTwo.
The starting code will thus look something like this (with empty implementations of
the three functions down at the bottom):
// set which characters we'll for yes/no
const char YES = 'Y';
const char NO = 'N';
// set the maximum number of input characters to clear on broken input
const int LineLen = 80;
// how many estimates we'll go through in our square root calculations
const int AccuracyIterations = 10;
// asks the user if they want to skip a section (Y or N)
// repeats the question (using do while) until they enter Y or y or N or n
// returns true if they want to skip, false otherwise
bool skipThis(string section);
// partOne uses a while loop to get a number greater than one from the user then returns it
// (repeats until a valid value is supplied)
float partOne();
// partTwo uses a for loop to approximate the square root of a number
// (each pass through the loop gets a better approximation than the previous pass,
// partTwo uses partOne to get the number)
void partTwo();
int main()
{
// run the two parts, giving the user chances to skip either or both
if (!skipThis("part one by itself")) {
float positiveNum;
positiveNum = partOne();
cout << "Your number was " << positiveNum << endl;
cout << "End of part one" << endl;
}
if (!skipThis("part two")) {
partTwo();
}
}
bool skipThis(string section)
{
return true; // <== we'll replace this when we write the real version of the function
}
float partOne()
{
return 0; // <== we'll replace this when we write the real version of the function
}
void partTwo()
{
}
|
Writing skipThis
(This code will replace the "return true;" we had in skipThis originally.)
This is meant to prompt the user to enter Y or N (for yes or no) and return
true if they said Y or false if they said N.
We'll use a do-while loop to repeat until they enter something valid,
and we'll convert the character they enter to uppercase (so they can enter y or n
in addition to Y or N).
The nice thing about char input is that it cannot cause cin to fail,
so we don't need to worry about cin.fail, cin.clear, and cin.ignore.
In this case, we'll use a boolean variable (validAnswer) to keep track of whether
or not we've seen valid input from the user. This will be set to false initially
(since we haven't seen any input from the user yet) and will be changed
to true once we have valid input.
Thus our loop structure is something like
char skipOrNot; // the variable where we'll store the user input
bool validAnswer = false; // do we have valid input yet?
do {
// get the user to enter their character, store it in skipOrNot
// convert it to uppercase
// if it's yes then we can simply return true (we're done, they want to skip)
// otherwise if it's no then we can set validAnswer to true
// (we have a valid answer, asking not to skip)
// otherwise we can display an error/try again message
} while (!validAnswer);
return false; // we got out of the loop because they said No to skipping
Remember you have the constants YES and NO, be sure to use those rather
than hard-coding 'Y' and 'N' inside your function.
Converting to uppercase is done by the 'toupper' function (from the cctype library, which
is included for you by the string library), e.g.
myvar = toupper(myvar); // where myvar is a char variable
Writing partOne
(This code will replace the "return 0;" we had in partOne originally.)
This will look more like our earlier exercises where a function gets and checks a number,
but we're doing it in a while loop instead of using recursion.
We'll need a float to store the user number in, and (like in skipThis) we'll use a boolean
variable to keep track of whether or not we've obtained a valid value from the user.
Our loop logic can thus look something like:
float finalNum;
bool validNumber = false;
while (!validNumber) {
// do your cout and cin to get the user's input
// if cin.failed then give them an error/try again message
// (but no recursive call this time, since the loop will take care of repeating)
// otherwise if the number is <= 1 give them an error/try again message
// (again, no recursive call in this lab)
// otherwise set validNumber to true, so the loop will stop
// when it gets checked at the beginning of the next pass
}
return finalNum;
Writing partTwo
We'll need variables to keep track of:
- the number we're computing the root of, e.g. originalNum (a float)
- our latest estimate, e.g. estimate (a float)
- the square of the latest estimate, e.g. square (a float)
- upper and lower bounds on the root values, e.g. lowerBound, UpperBound (also floats)
- which estimate # this is (estimate 1, estimate 2, etc), e.g. estimateNum (an int)
To get started, we need to call partOne and assign its return value to originalNum,
and as our initial lower/upper bounds on the square root of that we can use 1 and originalNum
(e.g. if originalNum is 12 we can safely assume its square root is somewhere in the range 1..12).
Our for loop will then go through each of the estimates, getting more accurate each time:
for (estimateNum = 1; estimateNum <= AccuracyIterations; estimateNum++) {
// set our new estimate to be (lowerBound + upperBound)/2
// set square to be (estimate * estimate)
// if square is bigger than originalNum
// then set upperBound = estimate
// otherwise if square is smaller than originalNum
// then set lowerBound = estimate
// otherwise
// tell the user we got the exact answer (!)
// and 'break' from the loop
}
output our final estimate, square, and the originalNum we were trying to compute the root of
Sample run
The output below shows a sample run where the user chose to skip part one and entered
12 as the number to compute the root of.
(User input illustrated using bold italics here just for clarity)
Do you want to skip part one by itself (Y or N): y
Do you want to skip part two (Y or N): n
Please enter a number greater than 1.0: 12
Guess number 1: 6.5
Guess number 2: 3.75
Guess number 3: 2.375
Guess number 4: 3.0625
Guess number 5: 3.40625
Guess number 6: 3.57812
Guess number 7: 3.49219
Guess number 8: 3.44922
Guess number 9: 3.4707
Guess number 10: 3.45996
Our final estimate was 3.45996, whose square is 11.9713 (aiming for 12)
|
As always, check you've followed code standards and do a make submit
Second half: pass-by-reference and nested loops (to be done in lab4.cpp)
For the second half of the lab (in lab4.cpp) we'll be practicing with pass-by-reference
parameters and nested for loops (loops inside loops).
The actual program behaviour will be getting the user to enter two
characters, figuring out which one is "smaller" (using < to test), and using nested
for loops to print a triangular display based on that.
Sample run
The output below shows a sample run where the user entered xp as their two characters.
(User input illustrated using bold italics here just for clarity)
Please enter two characters, e.g. Qv
xp
Using (p) and (x) as the chars
pqrstuvwx
qrstuvwx
rstuvwx
stuvwx
tuvwx
uvwx
vwx
wx
x
|
Basic setup:
The only library we'll need this time is iostream, but if you're using the "using std::cout;"
approach to simplify cout, cin, endl, etc then you'll need to add one more for noskipws
(which we'll discuss later).
We'll be creating three functions:
- swap: takes two pass by reference char parameters and exchanges their values
- getChars: takes two pass by reference char parameters, min and max, gets two characters from the
user and puts the smaller into min and the larger into max (order unimportant if they're equal)
- printTri: takes two (pass by value) char parameters and generates our triangular display
The behaviour of main is pretty straightforward:
- declare two char variables
- call getChars, passing the two variables (which getChars will fill in)
- call printTri, passing the two variables
And, of course, we'll need initial empty versions of the three functions down below main.
A sample setup might thus look like:
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
using std::noskipws;
// exchanges the values between the two parameters
void swap(char &x, char &y);
// get two characters from the user,
// put the smaller (according to <) into parameter min
// and the larger into parameter max
void getChars(char &min, char &max);
// print an alphabet triangle
void printTri(char xAxis, char yAxis);
int main()
{
// get the two characters and pass them to print the triangle
char x, y;
getChars(x, y);
printTri(x, y);
}
void swap(char &x, char &y)
{
}
void getChars(char &min, char &max)
{
}
void printTri(char xAxis, char yAxis)
{
}
|
The swap function:
Much like the swap function we considered in class to swap two ints, this:
- uses a variable to remember the old value of x
- puts y's old value into x
- puts x's old value into y
void swap(char &x, char &y)
{
char oldX = x;
x = y;
y = oldX;
}
|
The getChars function:
This is a simpler input function than when we're getting numbers from the user,
as char input can never cause cin to fail (thus we don't have to worry about
cin.fail, clear, ignore).
The function simply has to prompt the user to enter two chars and read them into the
two parameters (min and max).
Ordinarily cin for chars skips whitespace (spaces, tabs, etc), but we might want to
allow the user to enter these for use in our program, so we'll add >> noskipws
to our cin to prevent it from skipping whitespace.
If min > max then we need to reverse their order, so we'll call the swap function.
void getChars(char &min, char &max)
{
// get the two characters
cout << "Please enter two characters, e.g. Qv" << endl;
cin >> noskipws >> min >> max;
// if the one in max is smaller then swap them
if (max < min) {
swap(min, max);
}
}
|
The printTri function:
The printTri function uses an outer for loop to control which row of output
we're on right now, and an inner loop to control the characters we print for
the current row. Once the inner loop completes we can print an endl to finish
off the current row.
void printTri(char xAxis, char yAxis)
{
cout << "Using (" << xAxis << ") and (" << yAxis << ") as the chars" << endl;
for (char row = xAxis; row <= yAxis; row++) {
for (char col = row; col <= yAxis; col++) {
cout << col;
}
cout << endl;
}
}
|
One interesting note is how the < works when comparing characters:
- Each character has a unique internal code to represent it, e.g. the code
for 'A' is 65, the code for 'B' is 66, ..., the code for 'a' is 97, etc.
- When comparing characters using < it is done based on the codes for the two
characters, thus 'A' is less than 'B', both are less than 'a', etc.
- These codes come from the ASCII table (do a search for ascii if you're curious
about the ASCII standard, you'll find the table of all 128 codes).
Try experimenting with different character combinations to get a glimpse of the
order of characters in ascii (suggestions: try things like Wf or !5 or t~).
As always, check you've followed code standards and do a make submit