8.3. The C++ string Class

Time: 00:03:43 | Download: Large, Large (CC) Small | Streaming, Streaming (CC) | Slides (PDF)
Review

C++ has a string class that behaves very much like the Java String class (but notice that the C++ string class name begins with a lowercase letter). Introducing the string class before exploring classes in general may be a little confusing. However, string objects are an important data type. Presenting them now will allow us to use them as class members when we formally study classes in the next chapter, making our classes more realistic and useful. The early introduction relies on your experience with classes and methods from Java in CS 1400. Furthermore, we'll restrict our treatment of the string class to basic operations and terminology.

Both C++ and Java classes provide one significant advantage over C-strings: once created, a C-string has a maximum length beyond which it cannot grow. The size of the character array storing the C-string limits the C-string's length. Alternatively, instances of both string classes are dynamic and can grow when needed. The ability to grow suggests that string objects can manage their memory. However, in the case of Java, saying that a string can grow is a bit of an oversimplification as Java strings are immutable - they can't change. So, any operation that looks like it modifies a Java string actually creates a new one. Alternatively, When a C++ string needs to grow, it allocates a new, larger array with new, copies the existing data into the new array, and then deallocates the old array with delete. Therefore, growing an already large string can slow a program due to the data copy.

Making string Objects

#include <string>
#include <iostream>
#include <string>
using namespace std;
The string class header file. Any program - or, more accurately, any file in a multi-file program - that uses the string class must include the string header file. When multiple include directives appear in a file, which is quite common, programmers may list them in any order, but they must precede the using statement.

The string class defines ten constructor functions. Programs automatically call constructors whenever they create or instantiate a new object. Constructors can be complex, but often they only initialize the new object. The following table describes three of the most frequently used constructors.

Prototype Example Comments
string();
string s1;
Default constructor: builds an empty string
string(const char* s);
string s2("Hello, World!");
Conversion constructor: converts a C-string into a string
string(const string& s);
string s3(s2);
Copy constructor: makes a new string by copying a string
string class constructors. The string class has many constructors, but three do most of the work in day-to-day programming.

One of the many benefits classes provide programmers is hiding the complexity of the class's operations. They do this by separating the operation's interface from its implementation. So, programmers see what the class can do (the function prototypes) but not how it does it (the function bodies). Nevertheless, we can gain some insight into how the string constructors behave by examining the results of a simple program:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s1;			// default constructor
    string s2("hello");		// conversion constructor
    string s3(s2);		// copy constructor
    string s4("the quick brown fox jumps over the lazy dog");

    cout << s1.size() << " " << s1.length() << " " << s1.capacity() << endl;
    cout << s2.size() << " " << s2.length() << " " << s2.capacity() << endl;
    cout << s3.size() << " " << s3.length() << " " << s3.capacity() << endl;
    cout << s4.size() << " " << s4.length() << " " << s4.capacity() << endl;

    return 0;
}
(a)











0 0 15
5 5 15
5 5 15
43 43 47



(b)
A picture of a string object that stores the characters 'hello' but has a total capacity of 15. The last ten characters are empty in the sense that no known data is stored there - they contain whatever was in that memory location when the string was created.
(c)
string class constructors. Instances of the string class manage their memory and can automatically grow as the program adds characters to them. The ANSI C++ standard does not specify the implementation details for the string class, and differences between compilers are easy to find.
  1. The program automatically calls one of the string constructors as each of the variables/objects s1 through s4 are defined.
    The string member functions size and length are synonyms - they are different names for the same operation, which is getting the number of characters currently stored in a string object. The string member function capacity gets the number of characters the string can store before it must grow.
  2. The output from program (a) demonstrates an interesting behavior of many string implementations: the capacity of empty or short string objects is greater than their size. We can speculate that the string implementors chose this behavior to speed up the string-growing process. For example, suppose that a program defines a string and then iteratively adds characters to it. Growing the string for each addition will slow the program. But a string with extra capacity only needs to grow when that capacity is exhausted, and a few unused characters at the end of the string is an acceptable trade-off. The illustrated behavior, verified with Visual Studio and g++, continues until the string size reaches 15. At 15 or more characters, g++ creates new strings with equal size and capacity. For strings between 16 and 31, VS creates strings with a capacity of 31; for strings greater than 31, VS creates strings with a capacity of 47. I quit testing at this point, but presumably, VS continues adding increasingly large padding at the end of new strings.
  3. C++ implements string objects as arrays of characters. Furthermore, Many C++ compilers create new string objects with a capacity greater than the number of characters stored. The total number of elements in the array is the string's capacity. The blue characters are used or filled and correspond to the size or length of the string. The empty white boxes are unused - they contain whatever random bit pattern was in memory before the program created the string. Nevertheless, size ≤ capacity.
There are still some details we don't know: is the array null-terminated (i.e., is it a C-string), how does the object maintain the size and the capacity values, and what member variable points to the array? The ANSI C++ standard leaves these details to the various implementing compilers.

string Operators

C++ classes can give new meanings to existing operators, but the new meanings only apply when at least one operand is an instance of the defining class. Assigning a new meaning to an existing operator is called overloading and is the subject of chapter 10.

Operator Meaning Example
= Assignment
Converts a C-string to a string
s1 = s2;
s = "Hello, World!"
+ Concatenation s = s1 + s2;
+= Concatenation with assignment s += s2
== Equality if (s1 == s2) . . .
!= Inequality if (s1 != s2) . . .
<, <=, >, >= Relational if (s1 < s2) . . .
[] and at() Character access char c = s1[i];
<< and >> Input and output cout << s1;
cin >> s1;
string class operators. The string class overrides many basic C++ operators to make them work with string objects. But it's essential to note that while these operators work with the string class, they do not work with C-strings. Furthermore:
#include <iostream>
#include <string>
using namespace std;

int main()
{
	string	s1 = "Hello";
	string	s2 = "World";

	s1 += ", ";			// concatenation with assignment
	s2 += "!";			// concatenation with assignment

	if (s1 < s2)
		cout << s1 << endl;
	else
		cout << s2 << endl;

	if (s1 == s2)
		cout << "They are ==" << endl;
	else
		cout << "They are !=" << endl;

	if (s1 != s2)
		cout << "They are !=" << endl;
	else
		cout << "They are ==" << endl;

	string	s3 = s1 + s2;		// assignment and concatenation

	for (size_t i = 0; i < s3.length(); i++)
		cout << s3[i];		// no bounds checking

	cout << endl;

	for (size_t i = 0; i < s3.length(); i++)
		cout << s3.at(i);	// bounds checking

	return 0;
}
(a)













Hello,




They are !=




They are !=






Hello, World!




Hello, World!


(b)
string operator examples. The examples demonstrate some of the string operators and two string member functions.
  1. string class example code
  2. Program output; note the addition of space to align output with the corresponding statements