Why this matters
The earlier pattern lessons printed each character as soon as the loop reached it. For a small shape that works well. Once a row needs a border, padding or a separator, printing piece by piece gets awkward. You must print the spaces before the first character, and you must skip the separator after the last one. The code becomes a set of small rules that are easy to get wrong.
Building the row first solves this. You put the whole row into one variable, check it, change it if needed, and print it with one call. This is also the way programs build text for a report line, a log message or a line written to a file. Aligned tables, a later lesson in this module, use the same idea for columns.
Two ways to print a row
Consider a row of three stars. There are two ways to print it.
The first way is to print each cell. A cell is one character position in the row. The loop sends each star to the screen as soon as it is reached. Nothing is kept in memory between the steps.
The second way is to build the row in a string. A string is a value that holds text and can grow as you add characters to it. The loop adds each star to the string. When the row is complete, the program prints the whole string once.
Both ways produce the same screen output when nothing goes wrong. The difference lies in how the code is written, what it can control, and what it costs. The rest of this lesson shows each step.
The C++ type for text is std::string. It comes from the header <string>. You add a character to the end of a string with +=, so line += '*'; appends one star to the variable line. A new string is empty until you add something to it.
Printing each cell
Here is a triangle of stars with four rows. Row 1 has one star, row 2 has two, and so on. Each star is printed as the loop reaches it.
Press Run. There is no input for this program, so leave the Input box empty. The output is:
*
**
***
****The inner loop writes one star for each cell in the row. The '\n' after the inner loop ends the line. This version is short and clear. It works well for this shape.
Building the row as a string
Now the same triangle with each row built in a string. The inner loop adds stars to line, and the program prints line once per row.
Press Run. The output is:
*
**
***
****The output is identical to the first program. Here is what changed.
std::string line;creates an empty string at the start of each row. Because it is declared inside the outer loop, every row starts with a fresh, emptyline. The next section explains why this placement matters.line += '*';adds one star to the end ofline. The inner loop runsrowtimes, so the string grows torowcharacters.std::cout << line << '\n';prints the complete row. The screen receives the whole row in one step.
To check that the two programs really match, save each output to a file and compare the files. Running diff on the two saved outputs prints nothing, which means the files are identical. The diff command compares two files line by line and prints every difference it finds.
What changes and what stays the same
The two programs differ in a few ways that matter as patterns grow.
| Printing each cell | Building the row | |
|---|---|---|
| When text reaches the screen | Each character, as the loop runs | The whole row, after the loop |
| Calls to the output stream | One for each cell, plus one per row | One for each row, plus the newline |
| Can the row be checked first | No, it is already on screen | Yes, the row can be measured or changed |
| Extra memory | None | One string per row, which is freed when the row ends |
For a row of a few dozen characters, the extra memory is trivial. We did not time these two programs, so this lesson makes no claim about which one is faster for a given size. The practical benefit is control. Once the row is a string, you can ask how long it is, check whether it is empty, and add padding at the front or a border at the end.
One more difference matters when a program stops in the middle of a row. A row built in a string is either printed whole or not printed at all. A row printed cell by cell can stop halfway and leave a partial line on the screen.
Example 1: numbers with separators
This example prints a triangle of numbers. Row r prints the number r, repeated r times, with one space between the numbers. The spaces are the hard part. A space must go between two numbers, but not at the start or at the end of the row.
Printing cell by cell makes that rule awkward, because the program would have to know whether it is printing the first, middle or last number. Building the row makes it simple. Before adding a number, check whether the string already has something in it. If it does, add a space first.
Press Run. The output is:
1
2 2
3 3 3
4 4 4 4The new parts are these.
line.empty()returnstruewhen the string holds no characters. The check!line.empty()means "the string is not empty". The first number in a row sees an empty string, so no space is added before it. Every later number sees a non-empty string, so a space is added first.std::to_string(row)turns the number inrowinto a string of digits.line += std::to_string(row);appends those digits. You cannot append anintdirectly with+=and expect the digits, because theintwould be treated as a character code.
The output has no space at the end of any row. A program that prints a space after each number would leave one there, and the output would look the same on the screen but would not match exactly.
Example 2: a centred pyramid
A pyramid of stars needs spaces on the left of each row. Row 1 has three spaces and one star. Row 2 has two spaces and three stars. The last row has no spaces and seven stars. The number of spaces goes down by one each row, and the number of stars goes up by two.
Build the padding as a string. The constructor std::string(count, character) makes a new string that holds count copies of character. The string std::string(3, ' ') holds three spaces.
Press Run. The output is:
*
***
*****
*******The line std::string line(rows - row, ' '); does two jobs at once. It creates the string and fills it with the spaces needed for this row. The next line adds the stars after the spaces. When the program prints line, the spaces and stars come out in the right order.
The count rows - row is never negative here, because row never goes above rows. The next section shows what happens when a count does go below zero.
Example 3: a box around a word
The last example builds a box around the word hello. It has three lines: a top border, the word inside a frame, and a bottom border. Each line is built once and printed once.
Press Run. The output is:
+-------+
| hello |
+-------+Look at the details.
text.size()returns the number of characters intext, which is 5. The box needs one space on each side of the word, so the inside width istext.size() + 2, which is 7.std::size_tis the type that C++ uses for sizes and counts. Using it forwidthmatches the type returned bysize()."+" + std::string(width, '-') + "+"joins three pieces into one string. The first piece is a text literal, the text between double quotes. Adding a literal to astd::stringworks, because the+operator knows how to join the two. The result is+, then seven dashes, then+."| " + text + " |"does the same job for the middle line. The border is built once and used twice, so the top and bottom lines are always the same.
This example is a row-building pattern in its plainest form. Each line is a value that the program makes first and prints afterwards.
Common mistakes
Adding two text literals together
A text literal such as "a" is not a std::string. It is a fixed run of characters in the program. Two literals cannot be joined with +, and the compiler says so. The program below tries to do that.
#include <iostream>
#include <string>
int main() {
std::string line = "a" + "b"; // mistake: two string literals cannot be added
std::cout << line << '\n';
return 0;
}The compiler prints this error:
b5_plus.cpp: In function ‘int main()’:
b5_plus.cpp:5:28: error: invalid operands of types ‘const char [2]’ and ‘const char [2]’ to binary ‘operator+’
5 | std::string line = "a" + "b"; // mistake: two string literals cannot be added
| ~~~ ^ ~~~
| | |
| | const char [2]
| const char [2]The phrase const char [2] describes a fixed array of two characters, one letter and the end marker. The message says there is no + operator for two of them. The fix is to make one side a std::string, as in std::string("a") + "b". The program then prints ab.
A count below zero
std::string(count, character) needs a count of zero or more. If the count is negative, the library cannot build the string and stops the program. This version of the pyramid has a wrong count on the last row.
#include <iostream>
#include <string>
int main() {
const int rows = 3;
for (int row = 1; row <= rows; ++row) {
std::string pad(rows - row - 2, ' '); // mistake: goes below zero on row 3
std::cout << pad << "*\n";
}
return 0;
}On row 2 the count is 3 - 2 - 2, which is -1. The program stops with this message.
terminate called after throwing an instance of 'std::length_error'
what(): basic_string::_M_createThe first part says that the library threw an error called std::length_error, and that nothing caught it. The what() line gives the library's own short description. The process ends with exit code 134, which means the program was stopped by an abort signal.
The first line of output, a single * for row 1, does not appear in the run. The program stopped before the output was flushed. Standard output is held in a buffer when it goes to a pipe or a file, and an abort does not write the buffer out. When the output goes to a terminal, the line may appear. The fix is to make sure the count is never negative. Work out the count for the smallest and the largest row before you use it.
Declaring the string outside the row loop
The string must start empty for each row. If you declare it before the outer loop, the text from one row stays in the string and the next row is added to it.
#include <iostream>
#include <string>
int main() {
const int rows = 3;
std::string line; // mistake: declared outside the row loop
for (int row = 1; row <= rows; ++row) {
for (int col = 1; col <= row; ++col) {
line += '*';
}
std::cout << line << '\n';
}
return 0;
}The output is:
*
***
******Row 2 should have two stars. It has three, because the single star from row 1 is still in line. Row 3 should have three stars and has six. Each row keeps everything from the rows above it. The fix is to move std::string line; inside the outer loop, so that each row starts empty.
Practice
Exercise 1: Stars with spaces between them
Print a four-row triangle of stars with one space between the stars in each row. Build each row as a string and print it once. Do not print a space at the start or end of any row.
Show the answer
The answer adds a space before each star except the first one, using the empty() check from example 1.
#include <iostream>
#include <string>
int main() {
const int rows = 4;
for (int row = 1; row <= rows; ++row) {
std::string line;
for (int col = 1; col <= row; ++col) {
if (!line.empty()) {
line += ' ';
}
line += '*';
}
std::cout << line << '\n';
}
return 0;
}The output is:
*
* *
* * *
* * * *Exercise 2: A box with stars
Print a box around the word stop. Use * for the border instead of -. The box should be three lines high, and the inside of the frame should have one space on each side of the word.
Show the answer
The width of the border is the length of the word plus two spaces. The middle line joins the bars and the word with +.
#include <iostream>
#include <string>
int main() {
const std::string text = "stop";
const std::string border = "*" + std::string(text.size() + 2, '*') + "*";
std::cout << border << '\n';
std::cout << "* " + text + " *" << '\n';
std::cout << border << '\n';
return 0;
}The output is:
********
* stop *
********Exercise 3: Predict the output
Read this program and write down what it prints before you run it. Then run it to check.
#include <iostream>
#include <string>
int main() {
const int rows = 3;
std::string line;
for (int row = 1; row <= rows; ++row) {
for (int col = 1; col <= row; ++col) {
line += '*';
}
std::cout << line << '\n';
}
return 0;
}Show the answer
The string is declared outside the row loop, so it keeps its stars from one row to the next. Row 1 adds one star, row 2 adds two more and row 3 adds three more. The output is:
*
***
******Exercise 4: Fix the compile error
In a program that includes <string>, the line below does not compile, because two text literals cannot be joined with +. Change it so that it compiles and prints xy.
std::string s = "x" + "y";Show the answer
Make the first operand a std::string. The program below compiles without warnings under g++ -std=c++20 -Wall and prints xy.
#include <iostream>
#include <string>
int main() {
std::string s = std::string("x") + "y";
std::cout << s << '\n';
return 0;
}The output is:
xyExercise 5: A function that builds a row
Write a function makeRow that takes a count and a character, and returns a string with that many copies of the character. If the count is zero or less, the function returns an empty string. Use it to print rows of one to four stars, and print one row for a count of minus two.
Show the answer
The function checks the count before it calls the std::string constructor, so a negative count never reaches the library.
#include <iostream>
#include <string>
std::string makeRow(int count, char symbol) {
if (count <= 0) {
return "";
}
return std::string(count, symbol);
}
int main() {
for (int row = 1; row <= 4; ++row) {
std::cout << makeRow(row, '*') << '\n';
}
std::cout << "[" << makeRow(-2, '*') << "]\n";
return 0;
}The output is:
*
**
***
****
[]Summary
- Printing each cell sends characters to the screen as the loop runs. Building a row in a
std::stringlets the program check or change the whole row before printing it once. - Both methods give the same output for a simple pattern. The string method is easier when a row needs separators, padding or a border.
line.empty()tells you whether a string is still empty, which is how the first item in a row can be treated differently from the rest.std::string(count, character)makes a run of identical characters, and the count must be zero or more.- Declare the row string inside the outer loop, so that each row starts empty.
- Two text literals cannot be joined with
+. Start the expression with astd::string.