Chapter 9 Strings 0. Introduction C-strings are the kind of strings C++ gets from its C language heritage. A C-string is an ordinary array of char with the additional termination requirement. The last character position used in the array must be indicated by a null character (‘\0’) in the next position. The Standard C++ library provides the class string. This class is defined in the header <string>. This class has many useful member functions and overloaded operators that make string processing easier and more intuitive. This class is not properly part of the Standard Template Library. Nevertheless, the development of the STL influenced the design of the string class as it appears in the ANSI/ISO C++ Standard and in implementations provided by compiler developers. We will call the type of string C++ inherits from C, C-strings. We will call a C++ Standard string class object simply a “string”. 1. Outline of topics in the chapter 9.1 An Array Type for Strings C-string values and C-string variables Other Functions in <cstring> C-string input and output 9.2 Character Manipulation Character I/O The Member Functions get and put The putback, peek, and ignore Member Functions Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 2 Character-Manipulating Functions 9.3 The Standard class string Introduction to the Standard class string I/O with the class string string processing with class string Converting Between string objects and C-strings 2. General remarks on the chapter 9.1 An Array Type for Strings C-string values and C-string variables A type is a collection of values and allowable operations. The values that cstrings can have are things like "Hi, Mom!". These are also called cstring literals. We know about cstring operations, (copying, concatenation, to mention two) but we don't presently have any way to implement these in an easily used fashion, and don't have many details of cstrings. This section of the text provides these details. Before launching into the text's discussion of cstrings and arrays, some of the details of C and C++ "escape sequences" or special characters will be presented. The text's discussion of cstring variables points out that the null character '\0' is the terminator for cstring variables. The text points out that the library functions that process cstrings (including the iostream functions) use the null character as a sentinel indicating the last character of the cstring. The backslash (\) signals that the next character to be dealt with in a way different than it would be handled without the backslash. The backslash is called the 'escape character' because it escapes the normal meaning of certain characters, allowing them to have special meaning. (And it removes or escapes special meaning of characters that have special meaning, such as the backslash itself. To print a backslash, use two backslashes \\ in the cstring literal. Here the first backslash escapes the special meaning of the second backslash, which is inserted in the output stream.) Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 3 If we just put a 0 into some position in a cstring, the encoding for 0, 48 decimal, or 30 hexadecimal, is stored at that character position. (See Appendix 3 for the decimal encoding of the printing ASCII characters.) To cause the null character to be stored, we use the \ before the 0 to escape the usual meaning of 0. This tells the compiler that we want the numeric value of the null character to be embedded in the cstring. (The value of the null character really is zero, not the normal encoding of the character 0.) C++ provides a number of the 'escape characters'. we have already see \t, the tab character. It was used to help format output programs from previous chapters. The text discusses \0 and \n, the null and the new line characters. I include them all here because some of my students have had problems understanding what happened to certain characters in their output. If you, the instructor, are aware of these, you are in a better position to help the student. null newline horizontal tab vertical tab backspace carriage return digit formfeed \0 \n \t \v \b \r alert or bell backslash question mark single quote double quote octal number \a \\ \? \' \" \ooo \f Hex number \xhhh h = hex digit o = octal If you use a backslash in front of a character not mentioned here, the compiler should issue a warning. Borland’s BCC 5.5 does not, whereas g++ and VC++6.0 do issue warnings about an unknown or unrecognized escape sequence. In connection with the remark in the text on page 353 that the following declarations are not equivalent: char shortStringA[] = {'a', 'b', 'c'}; and char shortStringB[] = "abc"; A quick way (under Linux, UNIX TM and other UNIX-like operating systems) to convince the student that these are not equivalent is to put these declarations in a program Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 4 and send them to the screen. Using g++ under Linux, when I send endl to the output stream after each of these, I get: "abc" from the first output statement, then "abc" followed by several ugly characters from memory locations following the array, up to the next ‘\0’ (or the next memory location that is protected, when I get a segmentations violation). Running the program then piping the result to od, the Unix octal dump program, with the byte by byte output command line switch (od -b), I get 17:28:35:~/AW$ a.out | od -b 0000000 141 142 143 277 320 374 377 277 071 012 141 142 143 012 0000016 17:28:42:~/AW$ The 0000000 is the number (offset from the start) of the first byte, the 141, 142, and 143 are the octal encoding for 'a', 'b', and 'c'. Following these are numbers there are 7 octal numbers that are not ASCII characters. They have the 'high bit set' and generate the PC's extended characters (that I cannot reproduce here). There is a second sequence of 141, 142, and 143. These are the output from the second cstring. These are octal representations for 'a', 'b', and 'c'. The 00000016 is the octal number for decimal 14. This is the number of a byte that is one past the last number listed on the previous line. Other Functions in <cstring> The library functions declared in <cstring> are unlike the template libraries such as <vector>. The definitions are templates and are mostly in the header file. The cstring library is precompiled so it has its declarations mostly in the header and the definitions mostly precompiled and placed in libraries to which your program is linked after compiling. There is a host of cstring functions declared in the header <cstring> header file. In PJ Plauger's Standard C Library, I count 22 such functions in the string.h. All of functionality declared in <string.h> appears in the C++ <string> header file. Most Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 5 of these functions are variations on the <cstring> functions listed in the text, or their equivalents that move a chunk of raw memory. With just a little care, almost anything you need to do will be easily done with just the functions in the text. Any use of these functions requires considerable care to ensure the preconditions for these functions are met. (See the next section in the text "Defining Cstring Functions".) The most critical issue is that there must be space enough in the destination to hold the result. Typographical Error in Display 9.1 In the first printing there are a couple of typographical errors in Display 9.l. The three argument C-string functions all have names with the letter n in the middle. For example, strcpy(target, source, limit) should be strncpy(target, source, limit). The other names should be strnncat and strncmp. These changes will be made in the next printing. PITFALL: Dangers in Using functions defined in <cstring> A danger in using functions from <cstring> lies in using arrays of char without a null terminator as the preconditions prescribe. Each of the functions strcpy, strcat and strcmp depend on the terminating null character for their action. In addition, the iostream output function expects there to be a terminating null character. Otherwise, as we note elsewhere in this IRM, the output routine will run on until either we encounter a null character or protected memory. Here is an additional bit of warning about the strcmp function. I want to expand on the text's discussion of the strcmp function. The strcmp function is required only to return a positive number (not necessarily +1) if the second string argument is cstring argument is lexicographically greater (later in dictionary ordering) than the first cstring argument, and a negative number (not Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 6 necessarily –1) if the second cstring argument is lexicographically earlier than the first cstring argument. For example, x = strcmp( "aaron", "aardvark" ); assigns a positive number to x. This is not necessarily 1, though some implementation may indeed return 1 in this circumstance. If I were implementing strcmp, I would return the difference of the ASCII value of the first characters that differ in the two strings. If all the characters were the same, I would return 0. In fact, strcmp from Borland’s library returns the difference of the ASCII encoding values for the first characters that are different, whereas gnu and VC++ <cstring> versions of strcmp return 1 or -1 when the strings are different. Rule: Do not write code that depends on +1 or -1 being returned from strcmp! Pitfall toupper and tolower return int values (Page 374 of the text.) suggests this extension of the above rule: Extended Rule: Prior to using any library routine, carefully examines the declaration in the header, and examine the pre- and post-conditions in your library reference before you use a library function. C-string input and output Suppose non-digit input is received by the first loop in the following: #include <iostream> using namespace std; int main() { int x; cout << "enter some int values: \n"; while( cin >> x ) //intVariable ) { cout << "You pressed " << x << "\n"; } Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 7 cout << "finished" << endl; cout << "enter some more int values: \n"; while( cin >> x ) //intVariable ) { cout << "You pressed " << x << "\n"; } cout << "finished" << endl; return 0; } The first loop terminates when non-digit input is encountered. The input stream is set to stream state of bad, and any later input attempt fails. By convention, all input functionality is disabled with the stream state is set to other than a good state. We sometimes need to be able to restore the i/o state to good. To do this, we must reset the i/o state to goodbit and remove the offending characters. The iostream library has two member functions to change the stream state explicitly. Here the prototype of the first one. void clear(iostate state = goodbit); This function sets the state of the stream to the argument, where the function has a default argument of goodbit. Thus if you issue the call istreamObject.clear(); then the state flags are set to goodbit. A second function has prototype void setstate(iostate addstate); This function sets the state to whatever the state of the stream was prior to the call, (let’s calll this oldStreamState) to oldStreamState | addstate In this expression, the | is the bitwise OR operator. Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Example: You can reset the state of the input stream by… #include <iostream> using namespace std; int main() { int x; cout << "enter some int values: \n"; while( cin >> x ) //intVariable ) cout << "You pressed " << x << "\n"; cout << "finished" << endl; if(cin.bad()) { cout << "unrecoverable error\n"; exit(1); } else if(cin.fail()) { cout << "cin.fail() returned true.\n"; char discard; cin.clear(); // We must clear the stream flags first. cin >> discard; // The offending input is a char. It // remains on input queue and must be // removed and discarded before // continuing with input operations cout << "discarded bad char: " << discard << endl; } cout << "enter some more int values: \n"; while( cin >> x ) //intVariable ) cout << "You pressed " << x << "\n"; cout << "finished" << endl; Page 8 Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 9 return 0; } A last note on this section: Please be certain the student is aware of the necessity for providing space for the null terminator when using getline as well as in other C++ string uses. A typical call might look like this . cin.getline(stringVariable, MAX_CHARACTERS + 1); The getline member function overwrites the first argument (let's call it arg1) with the smaller of strlen(arg1) characters (including the null terminator) and MAX_CHARACTERS. A null character is always appended after the MAX_CHARACTERS number of characters. This caution is expressed in the text on page 362 in the getline sidebar. It bears emphasis. 9.2 Character Manipulation Character I/O and The Member Functions get and put The following code is a filter1 illustrating the clean, simple code that the iostream library provides. I illustrate some of the methods used with the iostream identifiers to control looping. This program will duplicate any file under UNIX. (Under other operating systems, this may not be the case. In particular, MS Windows uses a strongly typed file system. I don’t know much about the internals of any version of MS Windows, so I cannot guarantee that it will copy files there. It appears to do so.) // file: size.cc // Purpose: file-copying filter // usage: prog < infile > outfile #include <iostream> using namespace std; int main() { while (cin) 1 A filter is a stand-alone function that reads the standard input does some task to the information that it has read, then writes the standard output. In this case, we have the simplest possible filter, a filer that copies its input to the output. Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 10 cout.put(cin.get()); } It is interesting to note that cin.get() returns an int. The put member takes a char argument which causes the int to be converted to a char. If the loop in this example is rewritten: while (cin) cout << cin.get(); then the output is a sequence of int type ASCII codes for the input characters. A cast to char would be required to get the same behavior: This points up the need to know the return type for each member function. Here are full declarations (prototypes) for the get members of istream and the put member of ostream: ostream& ostream::put(char source); and int istream::get(); istream& istream::get(char& destination); I am using the first of the get functions, the text uses only the second. The text's usage ignores the istream reference return value, since this value is either ignored, in the first line of code below, or automatically used as in the second line of code following. operator << “This line ignores operator << return value.\n”; cout << “The value of x is “ << x ; The precedence of the << is left-to-right. The execution proceeds: (cout << “The value of x is “) << x ; The code inside the parentheses is executed, returning a reference to the ostream. This is used to carry out the outer << , which again returns an iostream reference, which is ignored. This is not unlike traditional C where the return value for printf is usually ignored: printf(“The value of x is %d “, x ); Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 11 Note that there are several other overloadings for get in istream and for put in ostream which do other important things that will be dealt with later. Care and reading the manual are necessary here. The putback, peek, and ignore Member Functions The putback function returns the argument character to the input stream then decrements the next-character pointer. The declaration of the putback member istream function is istream& putback(char c); The precondition for this function is that the char argument be the last character extracted, or if the istream state is not good, this function calls setstate(failbit) which may throw the exception, ios_base::failure, and return. If we ignore exceptions, the program will just terminate. We won’t discuss exceptions until Chapter 18. The instructor needs to be aware of this because inevitably some student will run into a situation where her program terminates inexplicably. The peek function allows us to examine a not-yet-read char from the input. This istream function has declaration int peek(); The function peek returns an EOF if at end of file, or if the function ios::good() returns false. Being able to examine a character prior to reading it is desirable, particularly when we want robust input that allows for human error at the keyboard. The ignore function extracts and discards a number of characters equal to the first argument or characters up to a character equal to the second argument. The declaration for this istream member is istream& ignore(streamsize n = 1, int delim = EOF); The type streamsize is an unsigned integral type. In most implementations, this is unsigned int. Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 12 Character-Manipulating Functions These functions are of two kinds, case conversion and classification functions. All these functions have an int parameter and return an int value.2 The declarations of the conversion functions are: int toupper(int); int tolower(int) The C++ library functions accept an int that is the ASCII representation for a character. If this character is not a lowercase letter, the return value is the int value of the representation of the character. If this character is a lowercase letter, the return value is the int value of the representation for the corresponding upper case letter. Implementations I have access to test for lowercase range then return the sum or difference of (‘A’ - ‘a’) and the parameter. If the parameter is not a lower case letter, the parameter is returned unchanged. In short, these functions take int parameters and return an int. Warn the student that blind use of these functions result in int output instead of char output. Example: char x; while(cin.get(x)) cout << toupper(x); This will generate numbers in the output instead of uppercase letters. The other functions have int parameters and return an int value, not as might be expected, a Boolean value. The value returned by a successful test is only guaranteed to This is an artifact of C++’s inheriting many of its libraries from C, including the library functions described in this section. Late addition of the Boolean type bool was also an impact. 2 Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 13 be nonzero. The returned value is specifically not guaranteed to be 1. Warn the student that code should not depend on the specific value returned by these functions. These functions classify depending on whether the argument is the encoding of a have one of several characteristics: uppercase lower case, digit, letter or digit, white space, punctuation, and printing character (not a control character or delete) 9.3 The Standard class string Introduction to the Standard class string The C++ string class was added, substantially in its present form, to the C++ (draft) Standard library circa 1994. For the Instructor: The class basic_string is a template that uses either template parameter char or wchar_t (wide characters) for the template parameter. This enables wide characters for support of international character sets (i.e., Unicode). In most C++ implementations I have seen, the name string is a typedef of the instantiation basic_string<char>. Rewriting string as a template was a major change from the version of string adopted in 1994. The string class overloads many operators: + and += for concatenation, = for assignment, <, <=, >, and >=, for string comparison use lexicographic ordering, and the indexing operation, [i], for access to characters in the string. There is no range checking for indexing on string objects. If range checking is needed, there is a string member function called at() that is range checked. It provides many member functions that provide the following useful functionality. See page 387, Display 9.7 for typical uses of functions providing the following functionality. several versions of find substring search search for any character from an argument string in the calling string object. search for any character not from an argument string in the calling string object. Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 14 extract a substring from the calling string object at a starting position with specified length. insert in the calling object an argument string at a specified position. remove a substring from the calling object starting at a specified position having specified length. I/O with the class string The insertion, <<,and extraction, >>, operators are overloaded for string objects. By default, extraction to a string object string s; cin >> s; ignores initial whitespace, just at extraction to a C-string. This operation then extracts the next sequence of nonwhite characters, and stops input at the next white space. There is a version of getline defined specifically for string objects. The syntax is string s; getline(cin, s); This version of getline is necessarily a stand-alone function. The committee of authors of the string library had two choices. They could have rewritten the iostream library to add a getline member function, or they could have written a stand-alone getline function. The iostream library was already written and stable. Just as you have to write a stand-alone overloading of the output operator for your classes, these authors chose to write a stand-alone overloaded version of getline for this pair of arguments. The getline function extracts from the input stream characters up to the delimiter character. The characters from the start to the delimiter are placed in the string variable, and the termination character is discarded. The delimiter character defaults to the newline character, ‘\n’. By supplying a third argument, the default termination character can be changed. It is a bad practice to mix cin >> x style input and getline. Note, however that pre-patchlevel 5 VC++ compilers 6.0 produces anomalous behavior for this code fragment when run from the command line. string line; for(int i = 0; .i < 5;.i++) { getline(cin, line); cout << line << endl; } Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 15 It is always a good idea to try this for yourself. However, it is essential that you try these ideas if you are using VC++ before taking these ideas to the classroom or assigning problems involving these ideas. string processing with class string With the exception of the palindrome code, I have talked about everything in this section o f the text earlier. Converting Between string objects and C-strings Several functions require C-string values as arguments. This section shows how to use the cstr() member to retrieve a null terminated C-string r-value from a string object. 3. Solutions to, and remarks on, selected Programming Projects 1. Format a Sentence. While it may be cleaner to use C++ Standard class string to do this problem, I chose to use only the features of <cstring> to do this problem. Students need to be able to use both <string> and <cstring> facilities. This program is to read in a (single) sentence (defined as a sequence of words terminated by a period (.). The sentence is to be formatted and written to the output. Formatting consists of capitalizing first words in a sentence and compressing all multiple blanks to single blanks. For this purpose, a line-break is a blank. No other capital letters are to be included in the output. A sentence is assumed end with a period and there are no other periods. Input: Get a 100 character line. The period is the terminating sentinel. Process: scan the line, capitalizing the first letter in the string, looking for multiple blanks which it eats, except for one, and newline == blank. Some questions asked during design: Do I replace new-lines with blanks? Answer: Defer this question until later. (Answered later: yes) What do I do with sentences that are longer than my line width on my output device? Answer: Scan for and replace new-lines with blanks before replacing multiple blanks with one blank, and allow wraps on output. Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 16 Output: Copy the modified string to the output. This solution uses only the C style string (C-string) features. The input is taken character by character. If a period (.) is encountered, the loop terminates. Process: First, any new-line characters are replaced by blanks. Next, all characters are made lower case. There is a one-time switch to capitalize the first non-blank. There is a state machine for compressing multiple blanks to single blanks: If we have a blank and haven't seen one since the last non-blank, set a variable, haveBlank. Next if we see a blank and haveBlank is already set, cause overwriting the current blank with the new one by backing up the index to the sentence array by one. Once we read a non-blank, if the haveBlank note is set to true, turn it off. These pieces, along with replacing the newlines with blanks, serve to compress each sequence of blanks and newlines to one blank. Finally, there is the matter of putting in a null character if the string is shorter than the limit, and putting in a period and null if the string is too long. Note: An alternative solution is to use a loop around cin >> word, where word is a Cstring variable. This ignores blanks and newlines. Then process the words much as I have processed the characters here. Then output the words with a single blank between. #include <iostream> #include <cstring> #include <cctype> void getSentence(char sentence[], int& size); // fetches characters for sentence until a period. The period is // put at the end of characters fetched. size is set to number of // characters fetched, including the period. void process(char str[], int size); // replaces newline by blanks, compresses multiple blanks to one // capitalizes the first letter of the first word, lower cases rest. int main() { using namespace std; char sentence[100]; Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 17 int size; cout << "Enter one sentence. The period is the sentinel to quit." << endl; getSentence(sentence, size); cout << endl; cout << sentence << endl; } void getSentence(char sentence[], int& size) { int haveBlank = false; int isFirstLetter = true; for(int i = 0; '.' != (sentence[i] = cin.get()) && i < 100; i++) { if('\n' == sentence[i]) sentence[i] = ' '; sentence[i] = tolower(sentence[i]); if(isFirstLetter && isalpha(sentence[i])) { isFirstLetter = false; sentence[i] = toupper(sentence[i]); } if(' ' == sentence[i] && !haveBlank) haveBlank = true; else if(' ' == sentence[i] && haveBlank) i--; else if(' ' != sentence[i] && haveBlank) haveBlank = false; } if(i < 99) sentence[i+1] = '\0'; else { sentence[98] = '.'; sentence[99] = '\0'; } size = i; } A typical run follows: Input: noW iS thE TiMe fOr gOOD MEN TO ComE TO aLl tHe aId oF ThE CounTRY. Output: Enter one sentence. The period is the sentinel to quit. Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 18 Now is the time for all good men to come to the aid of the country. 2. Count words and letters This program reads in a line of text, counts and outputs the number of words in the line and the number of occurrences of each letter. Define a word to be a string of letters delimited by white space (blank, newline, tab), a comma, or a period. Assume that the input consists only of characters and these delimiters. For purposes of counting letters, case is immaterial. Output letters in alphabetic order and only output those letters that occur. Algorithm development: The word count is carried out with a state machine. We enter with our state variable, inWord set to false, and our word count set to 0. while(input characters is successful) if we have encountered a blank, newline or a tab, we set state to false else if inWord is false, set state to true increment word count lowCase = tolower(inChar); charCount[int(lowCase) - int('a')]++; cout << wordCount << " " words" << endl; for(i = 0; i < 25; i++) if(charCount[i] != 0) cout << charCount[i] << " " << char(i + 'a')<< endl; Comments on the letter count code: We run tolower() on all characters entered. The data structure for the letter count is a 26-letter int array with indices in the range 0-25, calculated by index = int(character) - int('a') as each letter is read in, increment the appropriate array element. Output of the letter count is a loop running from 0-25, with an if statement that allows output if the array entry isn't zero. The word counting results of this agree with the Unix wc utility, and agree with hand counted simple files. I do not supply test results. Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 19 #include <iostream> #include <cctype> // character and word count. int main() { using namespace std; int inWord = false; int wordCount = 0; char ch; char lowCase; int charCount[26]; int i; //i is declared outside for to avoid warnings about //differences between new and old binding rules for the //for loop variable. for(i = 0; i < 26; i++) charCount[i] = 0; while('\n' !=(ch = cin.get())) { if(' ' == ch || '\n' == ch || '\t' == ch) inWord = false; else if(inWord == false) { inWord = true; wordCount++; } lowCase = tolower(ch); charCount[int(lowCase) - int('a')]++; } cout << wordCount << " words" << endl; for(i = 0; i < 25; i++) if(charCount[i] != 0) cout << charCount[i] << " " << char(i + 'a') << endl; return 0; } 3 First, Middle and Last Names Only notes are provided for this problem. Problem Statement: This program is supposed to accept first name, middle name or initial, then last name. Even if the middle name is only an initial, an initial followed by a comma, the output should be lastName, firstName, middleInitial. (period after middle initial) If the middle name is missing altogether the output should be Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 20 lastName, firstName Notes: This is easy if there is at least a middle initial. Using three variables, char firstName[20], middleName[20], lastName[20]; This works well for most names. The input is: cin >> firstName >> middleName >> lastName; and for output: cout << lastName << ", " << firstName << middleName[0] << '.' << endl; If the middle name is missing, the program must detect this (strlen), copy the middle name to the last name, and set the middle name to an empty string (or just arrange not to print the middle name). Or you can use <string> functions. 4 Text replacement This program accepts a line of text. It replaces all four-letter words in the string with "love". If the four-letter word is capitalized, the word love should be also. Only capitalize the first letter of the word. If the length is 4, call output(char firstLetter) that outputs Love or love depending on whether the firstLetter is uppercase or lower case. A Simple Minded “Almost Solution”: In developing this, I came upon a very simple minded solution that almost meets the requirements of the problem. I present that first. Caveat: This solution collapses all blanks to one blank, and requires that one must enter the end-of-file character to terminate the input, rather than "accepting a line of text." But it is a simple solution: #include <iostream> #include <cctype> void outputLove( char firstLetter ); // word has 4 letters, if firstLetter is uppercase, output Love // else output love int main() { using namespace std; char word[81]; Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 21 while ( cin >> word ) { if ( strlen(word) == 4 ) outputLove( word[0] ); else cout << word; cout << " "; } cout << endl; } void outputLove( char firstLetter ) { using namespace std; if ( isupper( firstLetter ) ) cout << "Love" ; else cout << "love"; } A “Better” Solution (Is it better? - It is certainly a more complicated solution. It does meet the requirements of the problem exactly. However, I am fond of the simpler solution. ) The following solution accepts a line of input. It uses cin member getline to fetch a line of input. A function, breakUp, separates the non-alphabetic and alphabetic substrings of the input line into an array of strings. The main function then examines the strings one at a time and if alphabetic and of length 4, prints 'love' or 'Love' depending on case of the string being replaced. The main part prints the string intact if it is of length not equal to 4 or it is non-alphabetic. Note that there is a stub included. It was well worth the trouble to create it to properly debug the main part of the program first. Then I could confidently debug the breakUp function. // File: ch10prg5.cc // Text Replacement #include <iostream> #include <cctype> void breakUp ( char line[], char list[][80], int & n ); // accepts line of up to 79 characters // returns in list the succession of null terminated words // and non-alpha character strings between them null terminated. // returns number of words in list in parameter n int main() { using namespace std; char line[80]; char list[80][80]; // ample space for 80 words of max length 79 Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings Page 22 int n; cin.getline(line, 80); breakUp( line, list, n); int i = 0; while ( i < n ) { if ( 4 == strlen( list[i] ) && isalpha(list[i][0] )) { if ( isupper(list[i][0]) ) cout << "Love"; else if ( islower( list[i][0] ) ) cout << "love"; } else cout << list[i]; i++; } cout << endl; } void breakUp ( char line[], char list[][80], int & number ) { // separator is anything not alpha int i = 0, k = 0, wordNumber = 0; while( i < strlen( line ) ) { k = 0; // after the start of the line, line[i] is non-alpha // extract the next non-alphabetic substring while ( !isalpha(line[i]) ) { list[wordNumber][k] = line[i]; i++; k++; } list[wordNumber][k] = '\0'; wordNumber++; k = 0; // line[i] is an alphabetic character. // extract the next alphabetic substring while ( isalpha (line[i]) ) { list[wordNumber][k] = line[i]; i++; k++; } list[wordNumber][k] = '\0'; wordNumber++; } number = wordNumber; } /* // // // STUB * STUB *STUB *STUB *STUB *STUB *STUB *STUB * It is WELL WORTH the trouble to create this to get the rest of the program running correctly. Note that array initializers would have been better than this long Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings // drawn out void breakUp { list[0][0] list[0][1] list[0][2] list[0][3] list[0][4] collection of assignments. ( char line[], char list[][80], int & n) = = = = = ' '; ' '; ' '; ' '; '\0'; list[1][0] list[1][1] list[1][2] list[1][3] = = = = 'n'; 'o'; 'w'; '\0'; list[2][0] = ' '; list[2][1] = '\0'; list[3][0] = 'i'; list[3][1] = 's'; list[3][2] = '\0'; list[4][0] = ' '; list[4][1] = '\0'; list[5][0] = 'a'; list[5][1] = '\0'; list[6][0] = ' '; list[6][1] = '\0'; list[7][0] list[7][1] list[7][2] list[7][3] list[7][4] = = = = = 'G'; 'o'; 'o'; 'd'; '\0'; list[8][0] = ' '; list[8][1] = '\0'; list[9][0] list[9][1] list[9][2] list[9][3] list[9][4] list[10][0] list[10][1] list[10][2] list[10][3] list[10][4] n = 11; } */ = = = = = 't'; 'i'; 'm'; 'e'; '\0'; = = = = = ' '; ' '; ' '; ' '; '\0'; Page 23 Instructor’s Resource Manual for Savitch Absolute C++ 03/08/16 Chapter 9 Strings 5. Sexist Language No solution is provided. 6. PD music CD convert index by title to index by composer No solution is provided. Page 24