On this page

Strings

Since module 2 you have used a std::string as one whole value: you stored it, printed it, joined two with + and read one from the input. A string is made of smaller pieces, the characters in it, and each character has a position.

In this lesson you open the string up. You read one character, change one, visit all of them in a loop, build a new string character by character, and search a string for a piece.

One character

A character is one letter, digit, space or other sign. The type that holds exactly one character is char. A character is written between single quotes.

#include <iostream>
#include <string>

int main() {
    char initial = 'K';
    std::string name = "Kofi";
    char gap = ' ';
    std::cout << initial << "\n";
    std::cout << name << "\n";
    std::cout << initial << gap << name << "\n";
}
K
Kofi
K Kofi

'K' and "K" look alike when they are printed, and they are values of different types.

Written Type Holds
'K' char exactly one character
"K" text a run of characters, which here happens to have one
"Kofi" text a run of four characters
"" text a run of no characters

Single quotes always hold exactly one character. There is no empty char, and ' ' is not empty: it is the space character.

The two types do not stand in for each other. A char variable cannot be given text in double quotes, even text of one character.

#include <iostream>

int main() {
    char initial = "K";
    std::cout << initial << "\n";
}

One character is written with two keys. '\n' is the line end you have printed since module 1. The backslash and the n together name one character.

Letters are numbers

The computer stores every character as a small whole number, called its code. A char is that number, with one extra rule: std::cout prints a char as the character that has this code.

Storing a char in an int shows the number.

#include <iostream>

int main() {
    char letter = 'a';
    int code = letter;
    std::cout << letter << " " << code << "\n";

    char next = letter + 1;
    std::cout << next << "\n";
    std::cout << letter + 1 << "\n";

    int value = '7' - '0';
    std::cout << value * 2 << "\n";
}
a 97
b
98
14

The codes were handed out in order, and that is the useful part.

Characters Codes
'0' to '9' 48 to 57
'A' to 'Z' 65 to 90
'a' to 'z' 97 to 122
' ' (the space) 32

You do not need to remember the numbers. Remember three facts: the digits are in order, the capital letters are in order, and the small letters are in order, with all the capitals before all the small letters.

Because the letters are in order, letter + 1 is the next letter. Look at the two lines that print it.

  • char next = letter + 1; works out 98 and stores it in a char. Printed, a char with the code 98 is b.
  • std::cout << letter + 1 prints 98. Arithmetic on a char gives an int, as arithmetic on an int and a double gives a double in module 2. An int is printed as a number.

So the result of arithmetic on characters is a character again only after it has been stored in a char.

Subtraction answers two questions that come up all the time.

  • c - '0' is the value of a digit. '7' - '0' is 55 - 48, which is 7. The character '7' is not the number 7.
  • c - 'a' is a small letter's place in the alphabet, counted from 0. 'a' - 'a' is 0 and 'd' - 'a' is 3.

Comparisons use the codes as well, so c >= 'a' && c <= 'z' is true exactly when c is a small letter. A char can also be a loop's counter.

#include <iostream>

int main() {
    for (char c = 'a'; c <= 'e'; ++c) {
        std::cout << c << " is letter number " << c - 'a' + 1 << "\n";
    }
}
a is letter number 1
b is letter number 2
c is letter number 3
d is letter number 4
e is letter number 5

How long is a string?

A string knows how many characters it holds. Write the variable, a dot, and size().

#include <iostream>
#include <string>

int main() {
    std::string town = "Leeds";
    std::string street = "12 Mill Lane";
    std::string nothing = "";
    std::cout << town.size() << "\n";
    std::cout << street.size() << "\n";
    std::cout << nothing.size() << "\n";
    town = town + "!";
    std::cout << town.size() << "\n";
}
5
12
0
6

town.size() is a call, like the calls you know, with one difference: the function belongs to the string written before the dot, and it answers for that string. It needs no values, and the parentheses stay.

What is counted:

  • Every character between the quotes counts, and that includes spaces. "12 Mill Lane" has two digits, eight letters and two spaces.
  • A digit is a character like any other. "12" has the size 2. The string does not know that it spells a number.
  • The quotes are not part of the string. They only mark where it starts and ends in the program.
  • A string with nothing in it has the size 0. It is called the empty string, and it is a perfectly good string. std::string line; with no value starts as the empty string, and getline gives one for an empty line of input.

The size is not fixed. After town = town + "!"; the string is one character longer, and size() says so.

The character at a position

The characters of a string are numbered, and the numbering starts at 0. The number of a character is its position. s[i] is the character at position i, and its type is char.

#include <iostream>
#include <string>

int main() {
    std::string word = "planet";
    std::cout << word[0] << "\n";
    std::cout << word[1] << "\n";
    std::cout << word[word.size() - 1] << "\n";
    word[0] = 'P';
    word[5] = 's';
    std::cout << word << "\n";
}
p
l
t
Planes
Position 0 1 2 3 4 5
Character p l a n e t

The string has 6 characters, and their positions are 0 to 5. The first character is at position 0. The last is at position size() - 1, never at position size().

word[0] = 'P'; is an assignment to one character. It replaces what was at that position. The string stays the same length, and the other characters stay where they are.

A position from size() upwards is not a character of the string. word[6] is not one of the six characters, and an empty string has none at all. C++ does not check the position for you: no message, no stop. Reading the position size() itself is defined: it gives the null character, a character with the code 0 that ends every string and is not one of its characters. Storing any other character there, or using a position beyond size(), is not defined: the program may print rubbish or change something else. Keeping every position below size() is your job, and it is the reason this lesson spends two sections on loops.

Counting from 0 has one pleasant result: a string of size n has the positions 0 to n - 1, which are exactly the values a loop for (int i = 0; i < n; ++i) gives i.

A count that cannot go below zero

size() does not give an int. It gives a value of a type called std::size_t, which is unsigned: it holds 0, 1, 2 and upwards, and it has no negative values at all.

That sounds harmless, because a size is never negative. The trouble starts with subtraction. Take 1 away from an unsigned 0 and there is no -1 to go to. The count wraps round to the largest value the type has, which is a very large number.

#include <iostream>
#include <string>

int main() {
    std::string word = "";
    std::cout << (word.size() - 1 < 0) << "\n";
    std::cout << (word.size() - 1 > 1000000) << "\n";

    int n = word.size();
    std::cout << (n - 1 < 0) << "\n";
    std::cout << (n - 1 > 1000000) << "\n";
}
0
1
1
0

The first two lines ask about word.size() - 1 for an empty string. It is not less than 0, and it is greater than a million. The whole of that expression is unsigned, because size() is, and so it cannot be -1.

The last two lines do the same sum after int n = word.size();. That statement stores the size in an int, and from then on the arithmetic is int arithmetic: 0 - 1 is -1, as you expect.

This matters in a loop. i < word.size() - 1 looks like "stop before the last character". For an empty string it means "stop before that very large number", and the loop sets off through positions that do not exist.

The rule for this course:

  • Store the size in an int first, and do all the arithmetic with that int. Every loop over a string in this course starts with int n = s.size();.
  • Comparing two sizes with each other, as in a.size() < b.size(), is safe. Nothing is subtracted.
  • If you do compare with size() directly, add on the other side and never subtract on the size() side: i + 1 < s.size() is safe where i < s.size() - 1 is not.

Visiting every character

To look at every character of a string, let a counter run through the positions. This is the pattern, and it is always the same three lines.

#include <iostream>
#include <string>

int main() {
    std::string code = "Flat 3B, North Wing";
    int n = code.size();
    int capitals = 0;
    for (int i = 0; i < n; ++i) {
        if (code[i] >= 'A' && code[i] <= 'Z') {
            ++capitals;
        }
    }
    std::cout << "Capitals: " << capitals << "\n";

    for (int i = n - 1; i >= n - 4; --i) {
        std::cout << code[i];
    }
    std::cout << "\n";
}
Capitals: 4
gniW

n is the size, stored in an int. i starts at 0 and the loop goes on while i < n, so i takes the values 0 to n - 1: every position, each once, and never one that is not there. For an empty string n is 0, the test is false at once, and the body does not run.

Inside the loop, code[i] is the character of this turn. Here it is compared with 'A' and 'Z', using the codes from the section before.

The second loop runs backwards over the last four positions. Going backwards needs i >= ... and --i, and it needs i and n to be int: the loop ends when i drops below its last value, and an unsigned counter could not drop below 0.

A string can be given to a function like any other value. The parameter's type is std::string.

#include <iostream>
#include <string>

int count_capitals(std::string line) {
    int n = line.size();
    int capitals = 0;
    for (int i = 0; i < n; ++i) {
        if (line[i] >= 'A' && line[i] <= 'Z') {
            ++capitals;
        }
    }
    return capitals;
}

int main() {
    std::string door = "Flat 3B, North Wing";
    std::cout << count_capitals(door) << "\n";
    std::cout << count_capitals("no capitals here") << "\n";
}
4
0

As with an int in module 6, the function gets a copy. line is a new string that starts with the same characters as door. If the function changed line[0], door would not change.

A character's code can also decide what is drawn. Here each letter of a word becomes a bar, and a later letter of the alphabet gets a taller bar.

#include "scimigo.h"
#include <string>
using namespace scimigo;

int main() {
    canvas(600, 400);
    std::string word = "planet";
    int n = word.size();
    for (int i = 0; i < n; ++i) {
        int place = word[i] - 'a' + 1;
        rectangle(60 + i * 80, 360 - place * 15, 60, place * 15, "teal");
    }
}

The tallest bar is the last one: t is letter number 20. The shortest is the third, for a.

Building a string

A string can grow. Start with an empty one and add to its end, one piece at a time. This is the pattern of a running total from module 5, with text in place of a number.

#include <iostream>
#include <string>

int main() {
    std::string phrase = "low tide at nine";
    std::string squeezed;
    int n = phrase.size();
    for (int i = 0; i < n; ++i) {
        if (phrase[i] != ' ') {
            squeezed += phrase[i];
        }
    }
    std::cout << squeezed << "\n";
    std::cout << squeezed.size() << "\n";

    std::string row;
    for (int i = 0; i < 3; ++i) {
        row += "ab";
        row.push_back('-');
    }
    std::cout << row << "\n";
}
lowtideatnine
13
ab-ab-ab-

std::string squeezed; starts empty. On each turn of the loop one character of phrase is looked at, and if it is not a space it is added to the end of squeezed.

Two ways to add to the end:

  • s += piece; adds a character or a whole string. It is the short spelling of s = s + piece;, as total += 5; is for numbers.
  • s.push_back(c); adds exactly one character. Like size(), it is written after a dot, and it belongs to the string before the dot.

Neither changes the characters that are already there. The size goes up by the number of characters added.

+ also joins a string and a character, on either side: row + '!' is row with an exclamation mark after it, and '!' + row has it in front. The rule from module 2 still holds: one side of the + must be a std::string variable.

Building is how a function makes a new string to give back. It declares an empty string, adds to it in a loop, and returns it.

#include <iostream>
#include <string>

std::string spaced_out(std::string word) {
    std::string result;
    int n = word.size();
    for (int i = 0; i < n; ++i) {
        result += word[i];
        if (i + 1 < n) {
            result += ' ';
        }
    }
    return result;
}

int main() {
    std::cout << spaced_out("tide") << "\n";
    std::cout << spaced_out("a") << "\n";
}
t i d e
a

Comparing strings

== and != work on strings: two strings are equal when they have the same characters in the same positions. < and > work too, and they put strings in the order of a dictionary, with one catch.

#include <iostream>
#include <string>

int main() {
    std::string pear = "pear";
    std::string plum = "plum";
    std::string plums = "plums";
    std::string capital = "Plum";
    std::string thirty = "30";
    std::string four = "4";
    std::cout << (pear < plum) << "\n";
    std::cout << (plum < plums) << "\n";
    std::cout << (plum == capital) << "\n";
    std::cout << (capital < pear) << "\n";
    std::cout << (thirty < four) << "\n";
}
1
1
0
1
1

To compare two strings, C++ goes through both from position 0 and stops at the first position where they differ. The string with the smaller character there, by its code, is the smaller string. Nothing after that position is looked at.

Comparison Decided at Because
"pear" < "plum" position 1 e comes before l
"plum" < "plums" the end of plum no position differs, and the shorter string ends first
"plum" == "Plum" position 0 p and P are different characters
"Plum" < "pear" position 0 P has the code 80 and p has 112
"30" < "4" position 0 the character 3 comes before the character 4

The catch is in the last two rows. The order is the order of the codes, and that is a dictionary's order only while all the letters are small, or all capital.

  • Every capital letter comes before every small letter. "Zoo" < "ant" is true.
  • Digits are compared as characters, not as numbers. "30" < "4" is true although 30 is the larger number.

Use these comparisons with at least one std::string variable in them. Two pieces of quoted text compared directly, as in "pear" < "plum", are not compared as strings at all.

Finding a piece and taking it out

Two functions that belong to a string do most of the work of pulling text apart.

s.find(piece) searches s for piece, which may be a character or a string, and gives the position where the first one starts. s.substr(start, length) gives a new string: length characters of s, beginning at position start. s.substr(start), with one value, takes everything from start to the end.

#include <iostream>
#include <string>

int main() {
    std::string entry = "colour=dark teal";
    std::size_t at = entry.find('=');
    std::string key = entry.substr(0, at);
    std::string value = entry.substr(at + 1);
    std::cout << at << "\n";
    std::cout << key << "\n";
    std::cout << value << "\n";
    std::cout << entry.substr(7, 4) << "\n";
}
6
colour
dark teal
dark

The = is at position 6. entry.substr(0, at) is the 6 characters before it: positions 0 to 5. entry.substr(at + 1) starts one after the = and runs to the end. Neither includes the = itself. entry is not changed by any of this: substr copies characters out.

find gives a position as a std::size_t, the unsigned type that size() gives, so at is declared with that type. Adding to it is safe. Subtracting from it needs the care of the section on counts.

When the piece is not there, find has no position to give. It gives a special value instead, named std::string::npos. Compare with that name before you use the result. Do not print it and do not use it as a position: it is the largest value of the type, far beyond the end of any string.

find can also start its search further along: s.find(piece, start) ignores everything before position start. That finds the second occurrence, and the third.

#include <iostream>
#include <string>

int main() {
    std::string line = "the cat sat on the mat";
    int found = 0;
    std::size_t at = line.find("at");
    while (at != std::string::npos) {
        ++found;
        std::cout << "at position " << at << "\n";
        at = line.find("at", at + 2);
    }
    std::cout << found << " found\n";
    if (line.find("dog") == std::string::npos) {
        std::cout << "no dog\n";
    }
}
at position 5
at position 9
at position 20
3 found
no dog

Each turn of the loop handles one occurrence and then searches again from just past it: at + 2, because "at" is two characters long. When nothing more is found, at becomes std::string::npos and the loop ends.

Questions about a character

The header <cctype> has functions that answer questions about one character, and two that convert one.

Call What it tells you
std::isalpha(c) is c a letter, capital or small?
std::isdigit(c) is c one of the digits 0 to 9?
std::isspace(c) is c a space, a tab or a line end?
std::toupper(c) the capital of c if c is a small letter; otherwise c unchanged
std::tolower(c) the small letter of c if c is a capital; otherwise c unchanged
#include <cctype>
#include <iostream>
#include <string>

int main() {
    std::string label = "Room 12b, 3rd floor";
    int n = label.size();
    int letters = 0;
    int digits = 0;
    for (int i = 0; i < n; ++i) {
        if (std::isalpha(label[i])) {
            ++letters;
        }
        if (std::isdigit(label[i])) {
            ++digits;
        }
    }
    std::cout << letters << " letters, " << digits << " digits\n";

    for (int i = 0; i < n; ++i) {
        label[i] = std::tolower(label[i]);
    }
    std::cout << label << "\n";

    std::cout << std::toupper('a') << "\n";
    char capital = std::toupper('a');
    std::cout << capital << "\n";
}
12 letters, 3 digits
room 12b, 3rd floor
65
A

Use the three questions as a condition, as in if (std::isalpha(c)). They answer with a number: 0 for no, and some number that is not 0 for yes. A condition treats any number that is not 0 as true. The number for yes is not always 1, so do not print it and do not add it to a count.

The two conversions hold the same kind of surprise. std::toupper and std::tolower return an int: the code of the character, not a char. That is why std::cout << std::toupper('a') prints 65. Store the result in a char, and it is a character again:

  • label[i] = std::tolower(label[i]); stores it back into the string, at the same position. This is the usual way to change the case of a whole string.
  • char capital = std::toupper('a'); stores it in a variable of its own.

Characters that are not letters pass through both conversions unchanged, so there is no need to test for a letter first. In the output above, the digits, the comma and the spaces of label are as they were.

To compare two characters without regard to case, convert both the same way and compare the results: std::tolower(a) == std::tolower(b). Two int codes compare as well as two characters do.

Your turn

Now try it yourself

Predict what a program will do, run it, and step through it line by line. The checks tell you when your own code works.

Loading the lab…

In this lab

  1. One character

    This program declares a char and a std::string, and prints them. Look at the quotes on each declaration, then decide what appears.

  2. Letters are numbers

    Every char is stored as a number, its code. The small letters have codes that follow one another, with 'a' at 97. The digits '0' to '9' follow one another too.

    This program does arithmetic on characters. Decide what each of its four lines of output is.

  3. How long is it?

    size() gives the number of characters in a string. Decide what each line of output is before you run the program.

  4. Position 0

    s[i] is the character at position i. The first character is at position 0.

    Decide what this program prints.

  5. Below zero

    s is the empty string, so s.size() is 0. The program asks questions about s.size() - 1, first directly and then after storing the size in an int.

    The first loop has a break that ends it after five turns. Its body does not use s[i]: there is no character to use.

    Decide what each of the five lines of output is.

  6. Count the vowels

    This program reads a line and counts how often the letter a appears in it. Press Run. For the line in the Input box it prints Count: 3.

    Change it to count all five vowels: a, e, i, o and u. A character is counted if it is any one of the five. Change the label as well, so that the program prints, for the same line:

    Vowels: 9
    

    The lines in this step have small letters only, so there are no capitals to think about. Keep the loop as it is: change what is tested inside it.

  7. Build it backwards

    This program reads a line and builds a copy of it, adding one character on each turn with +=. Then it prints the copy. Press Run and see the line come back unchanged.

    Change the program so that the string it builds is the line reversed. For lamp post it should print:

    tsop pmal
    

    Keep building the new string one character at a time. There is more than one way to do it: you can change the order in which the loop visits the positions, or you can change where each character is added.

  8. Which comes first?

    < on two strings asks whether the first comes before the second. The strings are compared character by character, by code. All the digits have smaller codes than all the capital letters, and all the capitals have smaller codes than all the small letters.

    The program prints five comparisons as 1 (true) or 0 (false). Decide each one.

  9. First name, last name

    This program reads a full name and prints the first name. It finds the space with find, and takes out the characters before it with substr. Press Run: it prints Maria.

    Change it so that it prints the last name first, then a comma and a space, then the first name:

    Okafor, Maria
    

    A name in this step is two words with exactly one space between them. The words can be of any length.

  10. Shout

    This program reads a line and prints it, one character at a time, unchanged.

    Change it to do two things.

    1. Print the line with every letter as a capital. Characters that are not letters stay as they are.
    2. On a second line, say how many digits the line contains.

    For the line in the Input box:

    GATE 14 OPENS AT 9
    Digits: 3
    

    The functions you need are in <cctype>. The program does not include that header yet.

  11. count_char

    main is written. It reads a line and asks a function, count_char, how often two different characters appear in it. Write that function above main.

    First line: int count_char(std::string text, char wanted)

    What it returns: how many of the characters of text are equal to wanted. A capital and a small letter are different characters, so count_char("Anna", 'a') is 1. For the empty string the answer is 0.

    The function returns its answer and prints nothing.

    The checks call your function themselves, with strings and characters that main never uses.

  12. is_palindrome

    A palindrome is a text that reads the same in both directions: level, noon, abcba.

    Write the function is_palindrome above main.

    First line: bool is_palindrome(std::string text)

    What it returns: true when text reads the same from its last character to its first as from its first to its last, ignoring capitals. So Level is a palindrome, because L and l count as the same letter. Otherwise false.

    • Every character takes part, spaces included. ab ba is a palindrome and ab a is not.
    • The empty string is a palindrome, and so is every string of one character: there is nothing in them that could differ.

    The function returns its answer and prints nothing.

    Think about the positions before you write. Which position is compared with position 0? With position 1? And what does your loop do when the size is 0?

  13. A Caesar cipher

    A Caesar cipher hides a message by moving every letter the same number of places along the alphabet. With a shift of 3, a becomes d, b becomes e, and so on. At the end the alphabet starts again: x becomes a, y becomes b and z becomes c.

    main is written. It reads a line and a number, and prints the line shifted by that number. Write the function it calls, above main.

    First line: std::string shift(std::string text, int k)

    Needs: k is 0 or more. It may be larger than 26.

    What it returns: a string as long as text, in which

    • every small letter has moved k places along the alphabet, going round from z to a as often as needed, and is still a small letter;
    • every capital letter has moved k places in the same way, and is still a capital;
    • every other character is unchanged.

    For the input in the Input box the program prints:

    Phhw dw Jdwh 9, Crh.
    

    Work with a letter's place in the alphabet, from 0 to 25, not with its code. Adding k to a place can give 26 or more, and there is an operator from module 2 that brings such a number back into the range 0 to 25.

  14. A letter histogram

    A histogram shows how often each thing occurs, as a row of bars. Write a program that reads one line and makes a histogram of its letters, twice: once printed, once drawn.

    Printed. Go through the letters from a to z in order. For each letter that occurs in the line at least once, print one row: the small letter, one space, and one # for each time it occurs. A capital counts as its small letter. Letters that do not occur get no row. For Banana bread:

    a ####
    b ##
    d #
    e #
    n ##
    r #
    

    Drawn. For each of those letters, also draw one bar, a teal rectangle standing on the line y = 280.

    • Every letter has its own place: the bar's left edge is at x = 20 + 20 * the letter's place in the alphabet, where a has the place 0 and z the place 25.
    • The bar is 16 wide, and 20 high for each time the letter occurs.
    • Its bottom is at y = 280, so its top is at 280 minus its height.

    So for Banana bread the bar for a is rectangle(20, 200, 16, 80, "teal"), and the bar for n starts at x = 280.

    A line with no letters in it prints nothing and draws no bars. No letter occurs more than 13 times.

    The starter sets up the canvas and reads the line. A char can be the counter of a for loop.

  15. to_number

    When a program reads 407 into a std::string, it has three characters, not a number. Write the function that turns such a string into an int.

    First line: int to_number(std::string digits)

    Needs: digits has between one and nine characters, and every one of them is a digit from 0 to 9.

    What it returns: the whole number those digits spell. to_number("407") is 407, to_number("0") is 0, and to_number("007") is 7.

    Work it out yourself from the characters, one digit at a time. Do not call a library function that converts text to a number.

    main reads a word and prints the number that is one larger, which it could not do with the string. For the input 407 it prints 408.

    A way to think about it: reading 407 from left to right, you have 4, then 40, then 407. Find what happens to the number so far each time a new digit arrives.

  16. Initials

    Write a function that gives the initials of a name.

    First line: std::string initials(std::string name)

    Needs: name is two words with exactly one space between them, and both words start with a letter.

    What it returns: a string of four characters: the first letter of the first word as a capital, a full stop, the first letter of the second word as a capital, and a full stop. initials("ada lovelace") is "A.L.".

    The letters come out as capitals whether they were typed as capitals or not.

    main reads a line and prints its initials.

  17. count_words

    Write a function that counts the words in a line.

    First line: int count_words(std::string line)

    What it returns: the number of words in line. A word is a run of characters that are not spaces. Words are separated by one or more spaces, and the line may also start or end with spaces. So each of these has two words:

    "low tide"      "low    tide"      "  low tide"      "low tide   "
    

    A line with no characters, or with nothing but spaces, has 0 words.

    main reads a line and prints the count. For the line in the Input box, which starts with two spaces and ends with one, it prints Words: 4.

    Counting spaces will not work, as the examples show. Think about what marks the start of a word: a character that is not a space, when the character before it was a space or when there was no character before it. A bool variable can remember, from one turn of the loop to the next, whether you are inside a word.

  18. First in the dictionary

    Write a program that reads words with >> until the input ends. The words are in small letters, and they may be spread over several lines.

    Then it prints two lines: the word that comes first in dictionary order, and the longest word. If several words share the greatest length, print the one that was read first.

    For the input in the Input box:

    First: apple
    Longest: banana
    

    cherry is as long as banana, and banana was read first.

    If the input has no words at all, print one line instead:

    No words
    

    The program has to remember two words while it reads. Decide what each of them holds before the first word has been read, and make sure that a starting value is never printed as though it were a word from the input.

  19. replace_all

    Write a function that replaces every occurrence of one piece of text with another.

    First line: std::string replace_all(std::string text, std::string from, std::string to)

    Needs: from is not empty. to may be empty, and may be longer or shorter than from.

    What it returns: text with every from in it replaced by to. replace_all("one fish, two fish", "fish", "bird") is "one bird, two bird".

    Three things to get right:

    • Occurrences are found from left to right, and they do not overlap. In "aaa" there is one "aa", at position 0, and then a single a is left: replace_all("aaa", "aa", "b") is "ba".
    • Text that has been put in is not searched again. replace_all("aa", "a", "aa") is "aaaa", and the function comes to an end.
    • When from does not occur, the result is text unchanged.

    The clearest way is to build a new string: copy the part of text before an occurrence, add to, carry on after the occurrence, and at the end copy what is left.

  20. A cipher with a keyword

    A Caesar cipher moves every letter by the same amount, which makes it easy to break. A keyword cipher moves each letter by a different amount, taken in turn from the letters of a keyword.

    Each letter of the keyword stands for a shift: a for 0, b for 1, and so on up to z for 25. The first letter of the message is shifted by the first letter of the keyword, the second by the second, and so on. When the keyword runs out, it starts again from its first letter.

    With the keyword cab the shifts are 2, 0, 1, 2, 0, 1 and so on. Before you write anything, work out by hand what tides becomes with that keyword. That is your first test case.

    Write two functions:

    • std::string encode(std::string message, std::string keyword)
    • std::string decode(std::string secret, std::string keyword)

    Keep it to small letters: the keyword is small letters only and is not empty, small letters in the message are shifted, and every other character is copied unchanged. Decide for yourself whether a character that is not a letter uses up a letter of the keyword, and write your decision in a comment.

    Two remainders will help. (place + k) % 26 goes round the alphabet. And with the keyword's size stored in an int m, i % m goes round the keyword: it is 0, 1, 2 and so on up to m - 1, and then 0 again.

    Nothing checks this one. When you have finished, go through the list below.