The Standard Template Library (STL) supplies a container class that aids in string operations and manipulations. The std::string
class not only dynamically resizes itself to cater to an application’s requirement but also supplies useful functions (that is, methods that help manipulate a string and that work using it). Thus, it helps programmers make use of standard, portable, and tested functionality in applications and focus on developing features that are critical to their applications.
In this lesson, you learn
• Why string manipulation classes are necessary
• How to work with the STL string
class
• How the STL helps you concatenate, append, find, and perform other string operations with ease
• How to use template-based implementation of the STL string
class
• The operator “”s
, which has been supported by the STL string
class since C++14
In C++, a string is an array of characters. In Lesson 4, “Managing Arrays and Strings,” a simple character array was defined as following:
char sayHello[] = {’H’,’e’,’l’,’l’,’o’,’ ’,’W’,’o’,’r’,’l’,’d’,’ ’};
sayHello
is the declaration of a character array (also called a string) of a fixed (that is, static) length. As you see, this buffer can hold a string of limited length; it would soon be overrun if you tried to hold a greater number of characters in it. Resizing this statically allocated array is not possible. To overcome this constraint, C++ supplies dynamic allocation of data. Therefore, a more dynamic representation of a string array is
char* dynamicStr = new char[arrayLen];
dynamicStr
is a dynamically allocated character array that can be instantiated to the length as stored in the value arrayLen
, which can be determined at runtime, and hence it can be allocated to hold data of variable length. However, should you want to change the length of the array at runtime, you would first have to deallocate the allocated memory and then reallocate to hold the required data.
Things get complicated if these char*
strings are used as member attributes of a class. Situations where an object of this class is assigned to another require the presence of a correctly programmed assignment operator and copy constructor. If these are not available, then two objects of the class might contain copies of a pointer, essentially pointing to the same char
buffer in memory. The destruction of one object, releasing the buffer, can result in the invalidation of the pointer contained by the other object. This will cause an application malfunction.
String classes solve these problems for you. The STL string classes std::string
, which models a character string, and std::wstring
, which models a wide character string, help you in the following ways:
• Reduce the effort of string creation and manipulation
• Increase the stability of the application being programmed by internally managing memory allocation details
• Feature copy constructor and assignment operators that automatically ensure that member strings get correctly copied
• Supply useful utility functions that help in truncating, finding, and erasing
• Provide operators that help in comparisons
• Let you focus on your application’s primary requirements rather than on string manipulation details
Note
Both std::string
and std::wstring
are actually template specializations of the same class—namely std::basic_string<T>
for types char
and wchar_t
, respectively. When you have learned how to use one, you can use the same methods and operators on the other.
You will soon learn some useful helper functions that STL string classes supply, using std::string
as an example.
The most commonly used string functions are
• Copying
• Concatenating
• Finding characters and substrings
• Truncating
• String reversal and case conversions, which are achieved using algorithms provided by the standard library
Tip
To use the class std::string
, include the header:
#include <string>
The string
class features many overloaded constructors and therefore can be instantiated and initialized in many different ways. For example, you can simply initialize or assign a constant character string literal to a regular STL std::string
object:
const char* constCStyleString = “Hello String!”; std::string strFromConst(constCStyleString);
or
std::string strFromConst = constCStyleString;
The preceding is similar to
std::string str2("Hello String!");
As is apparent, instantiating a string object and initializing it to a value do not require supplying the length of the string or the memory allocation details; the constructor of the STL string class automatically provides this information.
Similarly, it is possible to use one string
object to initialize another:
std::string str2Copy(str2);
You can also instruct the constructor of string
to accept only the first n characters of the supplied input string:
// Initialize a string to the first 5 characters of another std::string strPartialCopy(constCStyleString, 5);
You can also initialize a string to contain a specific number of instances of a particular character:
// Initialize a string object to contain 10 ’a’s std::string strRepeatChars (10, ’a’);
Listing 16.1 illustrates some popularly used std::string
instantiation and string copy techniques.
Input
0: #include <string> 1: #include <iostream> 2: 3: int main () 4: { 5: using namespace std; 6: const char* constCStyleString = “Hello String!”; 7: cout << “Constant string is: “ << constCStyleString << endl; 8: 9: std::string strFromConst(constCStyleString); // constructor 10: cout << “strFromConst is: “ << strFromConst << endl; 11: 12: std::string str2("Hello String!"); 13: std::string str2Copy(str2); 14: cout << “str2Copy is: “ << str2Copy << endl; 15: 16: // Initialize a string to the first 5 characters of another 17: std::string strPartialCopy(constCStyleString, 5); 18: cout << “strPartialCopy is: “ << strPartialCopy << endl; 19: 20: // Initialize a string object to contain 10 ’a’s 21: std::string strRepeatChars(10, ’a’); 22: cout << “strRepeatChars is: “ << strRepeatChars << endl; 23: 24: return 0; 25: }
Output
Constant string is: Hello String! strFromConst is: Hello String! str2Copy is: Hello String! strPartialCopy is: Hello strRepeatChars is: aaaaaaaaaa
Analysis
This code example illustrates how you can instantiate an STL string
object and initialize it to another string, creating a partial copy or initializing your STL string
object to a set of recurring characters. constCStyleString
is a C-style character string that contains a sample value, initialized in Line 6. Line 9 displays how easy std::string
makes it to create a copy using the constructor. Line 12 copies another constant string into a std::string
object str2
, and Line 13 demonstrates how std::string
has another overloaded constructor that allows you to copy a std::string
object, to get str2Copy
. Line 17 demonstrates how partial copies can be achieved, and Line 21 shows how std::string
can be instantiated and initialized to contain repeating occurrences of the same character. This code example briefly demonstrates how std::string
and its numerous copy constructors make it easy for a programmer to create strings, copy them, and display them.
Note
If you were to copy one c-style character strings into another, the equivalent of Line 9 in Listing 16.1 would be this:
const char* constCStyleString = “Hello World!”; // To create a copy, first allocate memory for one... char* copy = new char[strlen(constCStyleString) + 1]; strcpy(copy, constCStyleString); // The copy step // deallocate memory after using copy delete[] copy;
As you can see, the result is many more lines of code and a higher probability of introducing errors; in addition, you need to worry about memory management and deallocations. STL string
does all this for you—and more! You don’t need to use C-style strings at all.
The character contents of an STL string
class can be accessed via iterators or via array-like syntax where the offset is supplied, using the subscript operator ([]
). A C-style representation of string
can be obtained via the member function c_str()
. See Listing 16.2.
Input
0: #include <string> 1: #include <iostream> 2: 3: int main() 4: { 5: using namespace std; 6: 7: string stlString("Hello String"); // sample 8: 9: // Access the contents of the string using array syntax 10: cout << “Display elements in string using array-syntax: “ << endl; 11: for(size_t charCounter = 0; 12: charCounter < stlString.length(); 13: ++charCounter) 14: { 15: cout << “Character[” << charCounter << “] is: “; 16: cout << stlString[charCounter] << endl; 17: } 18: cout << endl; 19: 20: // Access the contents of a string using iterators 21: cout << “Display elements in string using iterators: “ << endl; 22: int charOffset = 0; 23: 24: for(auto charLocator = stlString.cbegin(); 25: charLocator != stlString.cend(); 26: ++charLocator) 27: { 28: cout << “Character [” << charOffset ++ << “] is: “; 29: cout << *charLocator << endl; 30: } 31: cout << endl; 32: 33: // Access contents as a const char* 34: cout << “The char* representation of the string is: “; 35: cout << stlString.c_str() << endl; 36: 37: return 0; 38: }
Output
Display elements in string using array-syntax: Character[0] is: H Character[1] is: e Character[2] is: l Character[3] is: l Character[4] is: o Character[5] is: Character[6] is: S Character[7] is: t Character[8] is: r Character[9] is: i Character[10] is: n Character[11] is: g Display elements in string using iterators: Character[0] is: H Character[1] is: e Character[2] is: l Character[3] is: l Character[4] is: o Character[5] is: Character[6] is: S Character[7] is: t Character[8] is: r Character[9] is: i Character[10] is: n Character[11] is: g The char* representation of the string is: Hello String
Analysis
This code displays multiple ways of accessing the contents of a string. Iterators are important in the sense that many of a string’s member function return their results in the form of iterators. Lines 11 through 17 display the characters in a string using array-like semantics via the subscript operator ([]
), which is implemented by the std::string
class. Note that with this operator, you need to supply the offset, as shown in Line 16. Therefore, it is important that you not cross the bounds of the string
; that is, you do not read a character at an offset beyond the length of the string. Lines 24 through 30 also print the content of the string character by character—but using iterators.
Tip
Listing 16.2 smartly avoids the tedious iterator declaration in Line 24 by using the keyword auto
, thereby telling the compiler to determine the type of iterator charLocator
using the return value of std::string::cbegin()
. If you were to explicitly program the type, the same line would appear as follows:
25: for(string::const_iterator charLocator = stlString.cbegin(); 26: charLocator != stlString.cend(); 27: ++charLocator ) 28: { 29: cout << “Character[”<<charOffset++ <<”] is: “; 30: cout << *charLocator << endl; 31: }
String concatenation can be achieved by using either the +=
operator or the append()
member function:
string sampleStr1("Hello"); string sampleStr2(” String! “); sampleStr1 += sampleStr2; // use std::string::operator+= // alternatively use std::string::append() sampleStr1.append(sampleStr2); // (overloaded for char* too)
Listing 16.3 demonstrates the use of these two variants.
Input
0: #include <string> 1: #include <iostream> 2: 3: int main() 4: { 5: using namespace std; 6: 7: string sampleStr1("Hello"); 8: string sampleStr2(” String!"); 9: 10: // Concatenate 11: sampleStr1 += sampleStr2; 12: cout << sampleStr1 << endl << endl; 13: 14: string sampleStr3(” Fun is not needing to use pointers!"); 15: sampleStr1.append(sampleStr3); 16: cout << sampleStr1 << endl << endl; 17: 18: const char* constCStyleString = “ You however still can!”; 19: sampleStr1.append(constCStyleString); 20: cout << sampleStr1 << endl; 21: 22: return 0; 23: }
Output
Hello String! Hello String! Fun is not needing to use pointers! Hello String! Fun is not needing to use pointers! You however still can!
Analysis
Lines 11, 15, and 19 show different methods of concatenating to an STL string. Note the use of the +=
operator used in Line 11 to append from another string
object. Lines 15 and 19 demonstrate overloaded variants of the string::append()
function, with Line 19 concatenating a C-style character string.
The STL string
class supplies a find()
member function with a few overloaded versions that help find a character or a substring in a given string
object:
// Find substring “day” in sampleStr, starting at position 0 size_t charPos = sampleStr.find("day”, 0); // Check if the substring was found, compare against string::npos if(charPos != string::npos) cout << “First instance of ”day” was found at position “ << charPos; else cout << “Substring not found.” << endl;
Listing 16.4 demonstrates the use of std::string::find()
.
Input
0: #include <string> 1: #include <iostream> 2: 3: int main() 4: { 5: using namespace std; 6: 7: string sampleStr("Good day String! Today is beautiful!"); 8: cout << “Sample string is:” << endl << sampleStr << endl << endl; 9: 10: // Find substring “day” - find() returns position 11: size_t charPos = sampleStr.find("day”, 0); 12: 13: // Check if the substring was found... 14: if(charPos != string::npos) 15: cout << “First instance ”day” at pos. “ << charPos << endl; 16: else 17: cout << “Substring not found.” << endl; 18: 19: cout << “Locating all instances of substring ”day”” << endl; 20: size_t subStrPos = sampleStr.find("day”, 0); 21: 22: while(subStrPos != string::npos) 23: { 24: cout << “”day” found at position “ << subStrPos << endl; 25: 26: // Make find() search forward from the next character onwards 27: size_t searchOffset = subStrPos + 1; 28: 29: subStrPos = sampleStr.find("day”, searchOffset); 30: } 31: 32: return 0; 33: }
Output
Sample string is: Good day String! Today is beautiful! First instance “day” at pos. 5 Locating all instances of substring “day” ”day” found at position 5 ”day” found at position 19
Analysis
Lines 11 through 17 display the simplest use of the find()
function, where it ascertains whether a particular substring is found in a string. This is done by comparing the result of the find()
operation against std::string::npos
(which is actually -1
) and indicates that the element searched for has not been found. When the find()
function does not return npos
, it returns the offset that indicates the position of the substring or character in the string. The code thereafter indicates how find()
can be used in a while
loop to locate all instances of a substring in an STL string. The overloaded version of the find()
function used here accepts two parameters: the substring or character to search for and the search offset that indicates the point from which find()
should search. You can manipulate the search by using this offset to get find()
to search for the next occurrence of the substring, as shown in Line 29.
Note
The STL string also features find functions such as find_first_of()
, find_first_not_of()
, find_last_of()
, and find_last_not_of()
that assist programmers in working with strings.
The STL string
class features a function called erase()
that can erase
• A number of characters when given an offset position and count:
string sampleStr("Hello String! Wake up to a beautiful day!"); sampleStr.erase(13, 28); // Hello String!
• A character when supplied with an iterator pointing to it:
sampleStr.erase(iCharS); // iterator points to a specific character
• A number of characters when given a range supplied by two iterators that bind the range:
sampleStr.erase(sampleStr.begin(), sampleStr.end()); // erase from begin to end
Listing 16.5 demonstrates different applications of the overloaded versions of the string::erase()
function.
Input
0: #include <string> 1: #include <algorithm> 2: #include <iostream> 3: 4: int main() 5: { 6: using namespace std; 7: 8: string sampleStr("Hello String! Wake up to a beautiful day!"); 9: cout << “The original sample string is: “ << endl; 10: cout << sampleStr << endl << endl; 11: 12: // Delete characters given position and count 13: cout << “Truncating the second sentence: “ << endl; 14: sampleStr.erase(13, 28); 15: cout << sampleStr << endl << endl; 16: 17: // Find character ’S’ using find() algorithm 18: string::iterator iCharS = find(sampleStr.begin(), 19: sampleStr.end(), ’S’); 20: 21: // If character found, ’erase’ to deletes a character 22: cout << “Erasing character ’S’ from the sample string:” << endl; 23: if(iCharS != sampleStr.end()) 24: sampleStr.erase(iCharS); 25: 26: cout << sampleStr << endl << endl; 27: 28: // Erase a range of characters using an overloaded version of erase() 29: cout << “Erasing a range between begin() and end(): “ << endl; 30: sampleStr.erase(sampleStr.begin(), sampleStr.end()); 31: 32: // Verify the length after the erase() operation above 33: if(sampleStr.length() == 0) 34: cout << “The string is empty” << endl; 35: 36: return 0; 37: }
Output
The original sample string is: Hello String! Wake up to a beautiful day! Truncating the second sentence: Hello String! Erasing character ’S’ from the sample string: Hello tring! Erasing a range between begin() and end(): The string is empty
Analysis
The listing indicates the three versions of the erase()
function. One version erases a set of characters when supplied a staring offset and count, as shown in Line 14. Another version erases a specific character, given an iterator that points to it, as shown in Line 24. The final version erases a range of characters, given a couple of iterators that supply the bounds of the range, as shown in Line 30. As the bounds of the range are supplied by begin()
and end()
member functions of the string
class that effectively include all the contents of the string, calling an erase()
on this range clears the string object of its contents. Note that the string
class also supplies a clear()
function that effectively clears the internal buffer and resets the string
object.
Tip
The iterator declarations in Listing 16.5 using auto
are wordy:
string::iterator iCharS = find(sampleStr.begin(), sampleStr.end(), ’S’);
You can simplify them like this:
auto iCharS = find(sampleStr.begin(), sampleStr.end(), ’S’);
The compiler automatically deduces the type of the variable iCharS
, given return value type information from std::find()
.
Sometimes it is important to reverse the contents of a string. Say you want to determine whether a string input by the user is a palindrome. One way to do it would be to reverse a copy of the string and then compare the two strings. You can easily reverse STL strings by using the generic algorithm std::reverse()
:
string sampleStr("Hello String! We will reverse you!"); reverse(sampleStr.begin(), sampleStr.end());
Listing 16.6 demonstrates the application of the std::reverse()
algorithm to std::string
.
Input
0: #include <string> 1: #include <iostream> 2: #include <algorithm> 3: 4: int main() 5: { 6: using namespace std; 7: 8: string sampleStr("Hello String! We will reverse you!"); 9: cout << “The original sample string is: “ << endl; 10: cout << sampleStr << endl << endl; 11: 12: reverse(sampleStr.begin(), sampleStr.end()); 13: 14: cout << “After applying the std::reverse algorithm: “ << endl; 15: cout << sampleStr << endl; 16: 17: return 0; 18: }
Output
The original sample string is: Hello String! We will reverse you! After applying the std::reverse algorithm: !uoy esrever lliw eW !gnirtS olleH
Analysis
The std::reverse()
algorithm used in Line 12 works on the bounds of the container that are supplied to it using the two input parameters. In this case, these bounds are the starting and ending bounds of the string
object, and the algorithm reverses the contents of the entire string. It would also be possible to reverse a string in parts by supplying the appropriate bounds as input. Note that the bounds should never exceed end()
.
String case conversion can be effected by using the algorithm std::transform()
, which applies a user-specified function to every element of a collection. In this case, the collection is the string
object itself. Listing 16.7 shows how to switch the case of characters in a string.
Input
0: #include <string> 1: #include <iostream> 2: #include <algorithm> 3: 4: int main() 5: { 6: using namespace std; 7: 8: cout << “Please enter a string for case-conversion:” << endl; 9: cout << “> “; 10: 11: string inStr; 12: getline(cin, inStr); 13: cout << endl; 14: 15: transform(inStr.begin(), inStr.end(), inStr.begin(), ::toupper); 16: cout << “The string converted to upper case is: “ << endl; 17: cout << inStr << endl << endl; 18: 19: transform(inStr.begin(), inStr.end(), inStr.begin(), ::tolower); 20: cout << “The string converted to lower case is: “ << endl; 21: cout << inStr << endl << endl; 22: 23: return 0; 24: }
Output
Please enter a string for case-conversion: > ConverT thIS StrINg! The string converted to upper case is: CONVERT THIS STRING! The string converted to lower case is: convert this string!
Analysis
Lines 15 and 19 demonstrate how efficiently std::transform()
can be used to change the case of the contents of an STL string.
The std::string
class, as you have learned, is actually a specialization of the STL template class std::basic_string<T>
. The template declaration of the container class basic_string
is as follows:
template<class _Elem, class _Traits, class _Ax> class basic_string
In this template definition, the parameter of utmost importance is the first one: _Elem
. This is the type collected by the basic_string
object. std::string
is therefore the template specialization of basic_string
for _Elem=char
, whereas wstring
is the template specialization of basic_string
for _Elem=wchar_t
.
In other words, the STL string
class is defined as
typedef basic_string<char, char_traits<char>, allocator<char> > string;
and the STL wstring
class is defined as
typedef basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> > string;
So, all string features and functions studied so far are actually those supplied by basic_string
, and they are therefore also applicable to the STL wstring
class.
Tip
You would use std::wstring
when programming an application that needs to better support non-Latin characters such as those in Japanese or Chinese.
Since C++14, the STL has supported operator “”s
, which converts the string contained within the quotes, in its entirety, to std::basic_string<t>
. This makes certain string operations intuitive and simple, as Listing 16.8 demonstrates.
Input
0: #include<string> 1: #include<iostream> 2: using namespace std; 3: 4: int main() 5: { 6: string str1("Conventional string initialization"); 7: cout << “Str1: “ << str1 << “ Length: “ << str1.length() << endl; 8: 9: string str2("Initialization using literals”s); 10: cout << “Str2: “ << str2 << “ Length: “ << str2.length() << endl; 11: 12: return 0; 13: }
Output
Str1: Conventional string Length: 20 Str2: Initialization using literals Length: 31
Analysis
Line 6 initializes an instance of std::string
from a regular character string literal. Note the null character in the middle of the string, which results in the word "initialization"
being completely missed by str1
. Line 9 uses operator “”s
to demonstrate how the instance str2
can now be used to contain (and therefore also manipulate) character buffers containing null characters, too, for instance.
Caution
Do not confuse the literal operator “”s
working with std::string
with that in std::chrono
, as seen here:
std::chrono::seconds timeInSec(100s); // 100 seconds std::string timeinText = “100”s; // string “100”
The former indicates time in seconds and is an integer literal, while the latter gives a string.
The std::string
class can be used to create copies, like this:
string strOriginal("Hello string"); string strCopy(strOriginal); string strCopy2(strCopy); string strCopy3; strCopy3 = strCopy2;
However, if you intend to use copies of strOriginal
to just perform read operations, then the copy step in itself is an avoidably expensive operation. The string_view
utility class allows you to view the original string without creating a copy of it:
string_strOriginal("Hello string view"); string_view strCopy(strOriginal); string_view strCopy2(strCopy); string_view strCopy3(strCopy2); cout << strCopy3; // Hello string view
string_view
provides a plethora of useful methods, as Listing 16.9 shows.
Input
0: #include<string_view> 1: #include<iostream> 2: using namespace std; 3: 4: int main() 5: { 6: string strOriginal("Use views instead of copies of strings"); 7: string_view fullView(strOriginal); // a full view 8: 9: cout << “The full view shows: “ << fullView << endl; 10: 11: cout << “The first instance of ’v’ is at position: “ 12: << fullView.find_first_of(’v’) << endl; 13: 14: cout << “Is view starting with ”Use”: “ << 15: (fullView.starts_with("Use") ? “true” : “false") << endl; // C++20 16: 17: cout << “Is view ending with ”strings”: “ << 18: (fullView.ends_with("strings") ? “true” : “false") << endl; // C++20 19: 20: string_view partialView(strOriginal.c_str(), 9); // partial view 21: cout << “Partial view shows: “ << partialView << endl; 22: 23: return 0; 24: }
Output
The full view shows: Use views instead of copies of strings The first instance of ’v’ is at position: 4 Is view starting with “Use”: true Is view ending with “strings”: true Partial view shows: Use views
Analysis
Usage of the string_view
class instead of string
is optional, and you should do it only if you wish to optimize performance. Listing 16.9 shows how you can view the string data without actually needing to duplicate it. Lines 12 and 15 demonstrate the use of the methods starts_with()
and ends_with()
, respectively; these methods were added to string_view
in C++20. Lines 20 and 21 demonstrate how a partial view of the original string may be constructed.
Tip
Use the string_view
class instead of a duplicate in std::string
in any case where you need to be viewing (that is, reading) a string but do not need to be modifying it.
In this lesson, you learned that the STL string
class is a container supplied by the Standard Template Library that helps with many string manipulation requirements. The advantage of using this class is apparent: This container class supplied by the STL framework implements memory management, string comparison, and string manipulation functions so programmers don’t have to.
Q. Can I use a range-based for loop on a std::string object?
A. Yes, you can use it to read one character after another, and it will make your code compact and readable! For example, in the code shown in Listing 16.2, a range version of the for
loop in Lines 24 through 30 would be:
for(const auto& charLocator : stlString) // range-based for { cout << “Character[” << charOffset ++ << “] is: “; cout << charLocator << endl; }
Q. I need to reverse a string by using std::reverse(). What header has to be included in order to be able to use this function?
A. <algorithm>
is the header that needs to be included for std::reverse()
to be available.
Q. What role does std::transform() play in converting a string to lowercase using the tolower() function?
A. std::transform()
invokes tolower ()
for the characters in the string
object that are within the bounds supplied to the transform function.
Q. Why do std::string and std::wstring feature exactly the same behavior and member functions?
A. They do so because they are both template specializations of the template class std::basic_string<T>
—for T=char
and T=wchar_t
, respectively.
Q. Is the comparison operator < of the STL string class case-sensitive or is it not case-sensitive?
A. The result of comparison using string
class operator <
are case-sensitive.
The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix E and be certain you understand the answers before continuing to the next lesson.
1. What STL template class does std::string
specialize?
2. If you were to perform a case-insensitive comparison of two strings, how would you do it?
1. Write a program to verify whether a word entered by a user is a palindrome. For example, ATOYOTA is a palindrome, as it reads the same backward and forward.
2. Write a program that tells the user the number of vowels in a sentence.
3. Write a program that converts every alternate character of a string into uppercase.
4. Write a program that has four string objects that are initialized to “I,” “Love,” “STL,” and “String.” Append them with a space in between and display the sentence.
5. Write a program that displays the position of every occurrence of the character a
in the string "Good day String! Today is beautiful!"
.
3.15.147.215