Embercode
✕
C++ PatternsLesson 9 of 14

35 min read

Building rows as strings

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.

C++ · editable

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.

C++ · editable

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, empty line. The next section explains why this placement matters.
  • line += '*'; adds one star to the end of line. The inner loop runs row times, so the string grows to row characters.
  • 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 cellBuilding the row
When text reaches the screenEach character, as the loop runsThe whole row, after the loop
Calls to the output streamOne for each cell, plus one per rowOne for each row, plus the newline
Can the row be checked firstNo, it is already on screenYes, the row can be measured or changed
Extra memoryNoneOne 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.

C++ · editable

Press Run. The output is:

1
2 2
3 3 3
4 4 4 4

The new parts are these.

  • line.empty() returns true when 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 in row into a string of digits. line += std::to_string(row); appends those digits. You cannot append an int directly with += and expect the digits, because the int would 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.

C++ · editable

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.

C++ · editable

Press Run. The output is:

+-------+
| hello |
+-------+

Look at the details.

  • text.size() returns the number of characters in text, which is 5. The box needs one space on each side of the word, so the inside width is text.size() + 2, which is 7.
  • std::size_t is the type that C++ uses for sizes and counts. Using it for width matches the type returned by size().
  • "+" + 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 a std::string works, 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_create

The 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:

xy

Exercise 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::string lets 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 a std::string.
Practice this topic: coding problems for C++ Patterns →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Floyd's triangle