Exercise 2-4
The framing program (Exercise 2-0) writes the mostly blank lines that separate the borders from the greeting one character at a time. Change the program so that it writes all the spaces needed in a single output expression.
Solution
The key to solving this problem is to use the string constructor statement that we learnt from chapter 1 on string.
std::string z(n, c) ;
This statements defines z as a variable of types std::string that initially contains n copies of the character c. Here, c must be a char, not a string or a string literal.
For example, the statement std::string z(10, ‘ ‘); will create a string variable z that is occupied of 10 space characters (char). We define char values using single quotes. (double quotes are for defining string values).
Bearing this in mind, I now demonstrate the high level Solution Strategy.
The program will need to do the followings:
- Ask the user for his first name so we can build the constant string greeting.
- Ask the user for the padding value so we can (in conjunction with the readily build greeting string) compute the total frame rows and columns. We will also be able to build the constant string padStringLeftRight and padStringTopBottom.
- Create an asymmetric for loop 1 r[0,rows) – the invariant r tells us which row we are at.
- At the beginning of each row, reset the invariant c to 0 – i.e. always start from column 0 (the first column of the row).
- Create an asymmetric while loop 2 c[0,cols) within loop 1 – the invariant c tells us which column we are at, keeping the row constant.
- We perform the following checks during each complete loop to determine what to output:
- Are we at the exact position to output the greeting string? If so, output the constant string greeting, and increment the invariant c by size of the string. Else…
- Are we on any border? if so, output a string literal of 1 asterisk character, and increment the invariant c by 1. Else…
- Are we on the greeting line? if so, output the constant string padStringLeftRight, and increment the invariant c by size of the string. Else…
- Output the constant string padStringTopBottom, and increment the invariant c by size of the string.
Notes:
- We use a for loop 1 (instead of while loop) because the increment of the invariant r (for rows) s always by 1 – using a for loop is easier and clearer. Nevertheless if we want it is also okay to use a while loop – just a matter of preference really.
- We use a while loop 2 (instead of a for loop) because the increment of the invariant c (for columns) changes depending on what we output. Sometimes the increment is 1 (asterisk), sometimes the size of the string (greeting, padStringLeftRight, padStringTopBottom).
Putting this all together, we now have our complete program.
#include <iostream> #include <string> // say what standard-library names we use using std::cin; using std::endl; using std::cout; using std::string; int main() { // ask for the person's name cout << "Please enter your first name: "; // read the name string name = ""; cin >> name; // ask user to provide the padding between frame border and greeting message cout << "Please specify an integer padding value: "; int inPad = 0; cin >> inPad; // build the message that we intend to write const string greeting = "Hello, " + name + "!"; // build the constant padding parameters const int pad = inPad; // padding // compute the number of rows to write const int rows = ( pad * 2 ) + 3; // compute the number of columns to write const string::size_type cols = greeting.size() + ( pad * 2 ) + 2; // build the padding string that lives on the same row as the greeting, between the greeting and the border. const string padStringLeftRight( pad, ' ' ); // build the padding string that lives between the top (or bottom) border and and greeting. const string padStringTopBottom( ( cols - 2 ) , ' ' ); // write a blank line to separate the output from the input cout << endl; // write 'rows' rows of output // invariant: we have written r rows so far for ( int r = 0; r != rows ; ++r ) { string::size_type c = 0; // invariant: we have written c characters so far in the current row while ( c != cols) { // Are we at the exact position where the greeting should start? if ( ( r == pad + 1 ) && ( c == pad + 1 ) ) { // output the greeting message cout << greeting; c += greeting.size(); } else { // are we on the border? if ( ( r == 0 ) // top row || ( r == rows - 1 ) // or bottom row || ( c == 0 ) // or left-most column || ( c == cols - 1 ) // or right-most column ) { // output the border asterisks cout << "*"; ++c; } else { // are we on the greeting line? if ( r == pad + 1) { // output the empty spaces between left (or right) border and the greeting cout << padStringLeftRight; c += padStringLeftRight.size(); } else { // output the empty spaces between top (or bottom) border and the greeting cout << padStringTopBottom; c += padStringTopBottom.size(); } } } } cout << endl; } return 0; }
Further explanations
Just little bit more explanation, just for peace of mind.
To compute the total rows required by the frame, we sum the:
- top and bottom padding (pad * 2),
- top border, bottom border, and the greeting lines (1 + 1 + 1 = 3).
Hence we have this line in the code:
// compute the number of rows to write const int rows = ( pad * 2 ) + 3;
To compute the total columns required by the frame, we sum the:
- The size of the greeting message ( greeting.size() )
- The size of the left and right paddings combined ( pad * 2 )
- The size of left and right borders ( 1 + 1 = 2 ),
Also, as per the book chapter 2.4 (page 22), it is good habit to use the type string::size_type (instead of integer) when computing the sum of string sizes.
Hence we have this line in the code:
// compute the number of columns to write const string::size_type cols = greeting.size() + ( pad * 2 ) + 2;
Result
The program compiles okay. Here is our output.
Please enter your first name: Johnny Please specify an integer padding value: 3 ********************** * * * * * * * Hello, Johnny! * * * * * * * **********************