8.9. Practice Problems Answers

  1. The statement char* string2 = string1; saves the address of the array string1 in the pointer string2 - it does not copy the contents of string1 to string2. Defining and initializing C-strings (b) illustrates the behavior of the assignment operator in conjunction with C-strings.
  2. char* string2 = string1;
    char string2[100];
    strcpy(string2, string1);
    Although not required by all compilers, you should add #include <cstring> at the program's top. Use strcpy_s(string2, 100, string1) on Windows platforms.
  3. The string class overrides the assignment operator. So, the statement string string2 = string1; copies the contents of string1 to string2.
  4. #include <iostream>
    #include <cstring>
    using namespace std;
    
    int main()
    {
    	char word[100];
    	char line[500] = "";
    	cout << "Enter a word w/o spaces, press Return to stop: ";
    
    	cin.getline(word, 100);
    	while (strlen(word) > 0)
    	{
    		strcat(line, word);
    		cin.getline(word, 100);
    	}
    
    	cout << line << endl;
    
    	return 0;
    }
    Although the program only reads one word at a time, it still uses getline because cin >> word doesn't properly null-terminate the input on all systems. The program uses line as an accumulator, so it must initialize it to an empty string. Use strcat_s(line, 500, word) on Windows platforms.
  5. #include <iostream>
    #include <string>
    using namespace std;
    
    int main()
    {
    	string word;
    	string line;
    	cout << "Enter a word w/o spaces, press Return to stop: ";
    
    	getline(cin, word);
    	while (word.length() > 0)
    	{
    		line += word;
    		getline(cin, word);
    	}
    
    	cout << line << endl;
    
    	return 0;
    }
    The program still uses line as an accumulator, but the default string constructor makes the string objects empty. The concatenation with the assignment operator, +=, concatenates the two strings and saves the result in line.
  6. strtok is one of the most difficult C-string functions to understand. Nevertheless, it makes solving parsing or tokenizing problems like this one easy.
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int main()
    {
        char line[100];
        cout << "Enter a line of text: ";
        cin.getline(line, 100);
    
        char* word = strtok(line, " ");
        while (word != nullptr)
        {
            cout << word << endl;
            word = strtok(nullptr, " ");
        }
    
        return 0;
    }
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int main()
    {
        char line[100];
        cout << "Enter a line of text: ";
        cin.getline(line, 100);
    
        for (char* word = strtok(line, " "); word != nullptr; word = strtok(nullptr, " "))
            cout << word << endl;
    
        return 0;
    }
    Although strtok solves simple parsing problems, it is destructive: it modifies the target string, line in this example, by inserting null-termination characters into it. See Parsing illustrated.
  7. Adding more delimiters (the characters that separate the tokens or words) is a trivial step - just change the second strtok argument from " " to " .,;:?!" - the order of the characters isn't important, but notice the space between the opening quotation mark and the period. strtok even allows multiple separators together. Now, try the following:
    Enter a line of text: see, the; quick!? red.,; jump over the lazy.... brown dog
    see
    the
    quick
    red
    jump
    over
    the
    lazy
    brown
    dog
  8. Unfortunately, the string class doesn't have an analog to C-string's strtok function, so we need to combine two string functions two solve the problem. One function will search for the delimiter, and the other will extract the token or word from the input. Although this approach makes the string solution a little more complex than the C-string version, it has the advantage that it isn't destructive (i.e., it doesn't modify the target string).

    While the text introduced a few string member functions (including find), it suggested that there are too many to cover in detail. To use any programming language effectively and efficiently, we must learn how to use its documentation. So, my approach for string member functions is describing some basic features of many functions and providing a link to my favorite, high-quality online C++ documentation. The following solution will use find and substr functions. Reading the documentation will help you understand the solutions.

    The find function quickly locates the space characters in the input, which implies that the most challenging part of the problem is extracting the individual words. The substr function extracts and returns a sub-string from the target or input string. It requires two arguments: the index location where the sub-string begins and the length of the sub-string. As the following picture illustrates, our solution requires two index variables: front and back.

    A picture of the input line 'see the quick red fox' with front pointing to the 'q' (index location 8) and back pointing to the space between 'quick' and 'red' (index location 13).

    back saves the result of the most recent call to find, while front is calculated from the previous find call. The difference between the two indexes, back - front, is the length of the sub-string: 13 - 8 = 5.

    #include <iostream>
    #include <string>
    using namespace std;
    
    int main()
    {
        string line;
        cout << "Enter a line of text: ";
        getline(cin, line);
    
        size_t front = 0;
        size_t back = line.find(" ");
        while (back != string::npos)
        {
            cout << line.substr(front, back-front) << endl;
            front = back + 1;
            back = line.find(" ", front);
        }
        cout << line.substr(front, back) << endl;
    
        return 0;
    }
    #include <iostream>
    #include <string>
    using namespace std;
    
    int main()
    {
        string line;
        cout << "Enter a line of text: ";
        getline(cin, line);
    
        size_t front = 0;
        size_t back;
        do
        {
            back = line.find(" ", front);
            cout << line.substr(front, back-front) << endl;
            front = back + 1;
        } while (back != string::npos);
    
        return 0;
    }
     
    These solutions are over-simplified a bit. The following solution solves a more complex problem and deals with the simplifications of those above.
  9. First, notice the addition of the if-statement highlighted in yellow. The if-statement prevents printing the zero-length sub-strings resulting from two or more adjacent delimiters. The Problem 8 solutions should have included this test; you can see the problem by running them and putting two or more spaces between the words.

    Next, we replace find with find_first_of. The functions' behavior is the same when they search for a single character (their first argument), so we could use find_first_of in the Problem 8 solutions. However, the functions behave differently when their first argument contains multiple characters: find searches for the entire sequence of characters while find_first_of searches for the individual characters.

    #include <iostream>
    #include <string>
    using namespace std;
    
    int main()
    {
        string line;
        cout << "Enter a line of text: ";
        getline(cin, line);
    
        size_t front = 0;
        size_t back;
        do
        {
            back = line.find_first_of(" .,;:?!", front);
            if (back - front > 0)
                cout << line.substr(front, back-front) << endl;
            front = back + 1;
        } while (back != string::npos);
    
        return 0;
    }
  10. The following program solves a more realistic problem than the previous ones and is more complex. Reviewing a few concepts will help you understand the program:
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int main()
    {
    	char line[100];
    	cout << "Enter a line of text: ";
    	cin.getline(line, 100);
    
    	char*	delimiter;
    	char*	next = nullptr;
    
    	if (line[0] == '"')					// a
    		delimiter = "\"";
    	else
    		delimiter = ",";
    
    	char* field = strtok_s(line, delimiter, &next);		// b
    	while (field != nullptr)				// c
    	{
    		cout << field << endl;
    		if (next != nullptr && next[0] == '"')		// d
    		{
    			next++;					// e
    			delimiter = "\"";			// f
    		}
    		else
    			delimiter = ",";
    		field = strtok_s(nullptr, delimiter, &next);
    	}
    
    	return 0;
    }
    There are many ways to solve the problem. For example, we could call strtok_s (or strtok_r) in the if-statements rather than setting delimiter. But the first call to strtok_s (highlighted in yellow) requires a different first argument than subsequent calls (highlighted in coral), and this constraint causes some unavoidable complexity in the program. Recall that the strtok_s's third argument, next in this program, saves the current parse location between calls.
    1. The line begins with a double quotation mark, so all characters from there to the closing quotation mark are one token or field.
    2. Get the first field.
    3. End the loop when all fields are extracted.
    4. Uses short circuit evaluation: if next is null, next[0] not evaluated. If the next character is ",
    5. skip the " and
    6. and include in field all characters until the next ".
  11. Some of the logic in the following program is similar to the logic used in Solutions 8 and 9. Please study those programs before proceeding. The program demonstrates the at function, but you can also use the index operator, [].
    #include <iostream>
    #include <string>
    using namespace std;
    
    int main()
    {
    	string line;
    	cout << "Enter a line of text: ";
    	getline(cin, line);
    
    	size_t front = 0;
    	size_t back;
    
    	do
    	{
    		if (line.at(front) == '"')				// a
    		{
    			front++;					// b
    			back = line.find_first_of("\"", front);		// c
    		}
    		else
    			back = line.find_first_of(",", front);		// d
    		if (back - front > 0)
    			cout << line.substr(front, back-front) << endl;	// e
    		front = back + 1;					// f
    	} while (back != string::npos);					// g
    
    	return 0;
    }
    1. Detects an opening ".
    2. Skips the opening ".
    3. Finds the closing ".
    4. Finds the next ,.
    5. Extracts and prints the next field.
    6. Advances front past the current delimiter in preparation for the next search.
    7. Terminates the loop when the end of the string is found.