|

Most
C++ compilers come with a class library that includes
a large set of classes for data manipulation. A
standard component of a class library is a String
class.
C++
inherited the null-terminated string and the library
of functions that includes strcpy() from C, but
these functions aren’t integrated into an object-oriented
framework. A String class provides an encapsulated
set of data and functions for manipulating that
data, as well as accessor functions so that the
data itself is hidden from the clients of the String
class.
If
your compiler doesn’t already provide a String class—and
perhaps even if it does—you might want to write
your own. The remainder of this unit discusses the
design and partial implementation of String classes.
At
a minimum, a String class should overcome the basic
limitations of character arrays. Like all arrays,
character arrays are static. You define how large
they are. They always take up that much room in
memory even if you don’t need it all. Writing past
the end of the array is disastrous.
A
good String class allocates only as much memory
as it needs, and always enough to hold whatever
it is given. If it can’t allocate enough memory,
it should fail gracefully.
Listing
11.12 provides a first approximation of a String
class.
Listing
11.12. Using a String class.
1:
//Listing 11.12
2:
3:
#include <iostream.h>
4:
#include <string.h>
5:
6:
// Rudimentary string class
7:
class String
8:
{
9:
public:
10:
// constructors
11:
String();
12:
String(const char *const);
13:
String(const String &);
14:
~String();
15:
16:
// overloaded operators
17:
char & operator[](unsigned short offset);
18:
char operator[](unsigned short offset) const;
19:
String operator+(const String&);
20:
void operator+=(const String&);
21:
String & operator= (const String &);
22:
23:
// General accessors
24:
unsigned short GetLen()const { return itsLen; }
25:
const char * GetString() const { return itsString;
}
26:
27:
private:
28:
String (unsigned short); // private constructor
29:
char * itsString;
30:
unsigned short itsLen;
31:
};
32:
33:
// default constructor creates string of 0 bytes
34:
String::String()
35:
{
36:
itsString = new char[1];
37:
itsString[0] = ‘\0’;
38:
itsLen=0;
39:
}
40:
41:
// private (helper) constructor, used only by
42:
// class methods for creating a new string of
43:
// required size. Null filled.
44:
String::String(unsigned short len)
45:
{
46:
itsString = new char[len+1];
47:
for (unsigned short i = 0; i<=len; i++)
48:
itsString[i] = ‘\0’;
49:
itsLen=len;
50:
}
51:
52:
// Converts a character array to a String
53:
String::String(const char * const cString)
54:
{
55:
itsLen = strlen(cString);
56:
itsString = new char[itsLen+1];
57:
for (unsigned short i = 0; i<itsLen; i++)
58:
itsString[i] = cString[i];
59:
itsString[itsLen]=’\0';
60:
}
61:
62:
// copy constructor
63:
String::String (const String & rhs)
64:
{
65:
itsLen=rhs.GetLen();
66:
itsString = new char[itsLen+1];
67:
for (unsigned short i = 0; i<itsLen;i++)
68:
itsString[i] = rhs[i];
69:
itsString[itsLen] = ‘\0’;
70:
}
71:
72:
// destructor, frees allocated memory
73:
String::~String ()
74:
{
75:
delete [] itsString;
76:
itsLen = 0;
77:
}
78:
79:
// operator equals, frees existing memory
80:
// then copies string and size
81:
String& String::operator=(const String &
rhs)
82:
{
83:
if (this == &rhs)
84:
return *this;
85:
delete [] itsString;
86:
itsLen=rhs.GetLen();
87:
itsString = new char[itsLen+1];
88:
for (unsigned short i = 0; i<itsLen;i++)
89:
itsString[i] = rhs[i];
90:
itsString[itsLen] = ‘\0’;
91:
return *this;
92:
}
93:
94:
//nonconstant offset operator, returns
95:
// reference to character so it can be
96:
// changed!
97:
char & String::operator[](unsigned short offset)
98:
{
99:
if (offset > itsLen)
100:
return itsString[itsLen-1];
101:
else
102:
return itsString[offset];
103:
}
104:
105:
// constant offset operator for use
106:
// on const objects (see copy constructor!)
107:
char String::operator[](unsigned short offset) const
108:
{
109:
if (offset > itsLen)
110:
return itsString[itsLen-1];
111:
else
112:
return itsString[offset];
113:
}
114:
115:
// creates a new string by adding current
116:
// string to rhs
117:
String String::operator+(const String& rhs)
118:
{
119:
unsigned short totalLen = itsLen + rhs.GetLen();
120:
String temp(totalLen);
121:
for (unsigned short i = 0; i<itsLen; i++)
122:
temp[i] = itsString[i];
123:
for (unsigned short j = 0; j<rhs.GetLen(); j++,
i++)
124:
temp[i] = rhs[j];
125:
temp[totalLen]=’\0';
126:
return temp;
127:
}
128:
129:
// changes current string, returns nothing
130:
void String::operator+=(const String& rhs)
131:
{
132:
unsigned short rhsLen = rhs.GetLen();
133:
unsigned short totalLen = itsLen + rhsLen;
134:
String temp(totalLen);
135:
for (unsigned short i = 0; i<itsLen; i++)
136:
temp[i] = itsString[i];
137:
for (unsigned short j = 0; j<rhs.GetLen(); j++,
i++)
138:
temp[i] = rhs[i-itsLen];
139:
temp[totalLen]=’\0';
140:
*this = temp;
141:
}
142:
143:
int main()
144:
{
145:
String s1("initial test");
146:
cout << "S1:\t" << s1.GetString() <<
endl;
147:
148:
char * temp = "Hello World";
149:
s1 = temp;
150:
cout << "S1:\t" << s1.GetString() <<
endl;
151:
152:
char tempTwo[20];
153:
strcpy(tempTwo,"; nice to be here!");
154:
s1 += tempTwo;
155:
cout << "tempTwo:\t" << tempTwo <<
endl;
156:
cout << "S1:\t" << s1.GetString() <<
endl;
157:
158:
cout << "S1[4]:\t" << s1[4] <<
endl;
159:
s1[4]=’x’;
160:
cout << "S1:\t" << s1.GetString() <<
endl;
161:
162:
cout << "S1[999]:\t" << s1[999] <<
endl;
163:
164:
String s2(" Another string");
165:
String s3;
166:
s3 = s1+s2;
167:
cout << "S3:\t" << s3.GetString() <<
endl;
168:
169:
String s4;
170:
s4 = "Why does this work?";
171:
cout << "S4:\t" << s4.GetString() <<
endl;
172:
return 0;
173:
}
OUTPUT:
S1: initial test
S1: Hello world
tempTwo: ; nice to be here!
S1: Hello world; nice to be here!
S1[4]: o
S1: Hellx World; nice to be here!
S1[999]: !
S3: Hellx World; nice to be here! Another string
S4: Why does this work?
ANALYSIS: Lines 7-31 are the declaration
of a simple String class. Lines 11-13 contain three
constructors: the default constructor, the copy
constructor, and a constructor that takes an existing
null-terminated (C-style) string. This String class
overloads the offset operator ([ ]), operator plus
(+), and operator plus-equals (+=). The offset operator
is overloaded twice: once as a constant function
returning a char and again as a nonconstant function
returning a reference to a char.
The
nonconstant version is used in statements such as
SomeString[4]=’x’;
as
seen in line 159. This enables direct access to
each of the characters in the string. A reference
to the character is returned so that the calling
function can manipulate it.
The
constant version is used when a constant String
object is being accessed, such as in the implementation
of the copy constructor, (line 63). Note that rhs[i]
is accessed, yet rhs is declared as a const String
&. It isn’t legal to access this object by using
a nonconstant member function. Therefore, the reference
operator must be overloaded with a constant accessor.
If
the object being returned were large, you might
want to declare the return value to be a constant
reference. However, because a char is only one byte,
there would be no point in doing that.
The
default constructor is implemented in lines 33-39.
It creates a string whose length is 0. It is the
convention of this String class to report its length
not counting the terminating null. This default
string contains only a terminating null.
The
copy constructor is implemented in lines 63-70.
It sets the new string’s length to that of the existing
string—plus 1 for the terminating null. It copies
each character from the existing string to the new
string, and it null-terminates the new string.
Lines
53-60 implement the constructor that takes an existing
C-style string. This constructor is similar to the
copy constructor. The length of the existing string
is established by a call to the standard String
library function strlen().
On
line 28, another constructor, String(unsigned short),
is declared to be a private member function. It
is the intent of the designer of this class that
no client class ever create a String of arbitrary
length. This constructor exists only to help in
the internal creation of Strings as required, for
example, by operator+=, on line 130. This will be
discussed in depth when operator+= is described,
below.
The
String(unsigned short) constructor fills every member
of its array with NULL. Therefore, the for loop
checks for i<=len rather than i<len.
The
destructor, implemented in lines 73-77, deletes
the character string maintained by the class. Be
sure to include the brackets in the call to the
delete operator, so that every member of the array
is deleted, instead of only the first.
The
assignment operator first checks whether the right-hand
side of the assignment is the same as the left-hand
side. If it isn’t, the current string is deleted,
and the new string is created and copied into place.
A reference is returned to facilitate assignments
lik
String1
= String2 = String3;
The
offset operator is overloaded twice. Rudimentary
bounds checking is performed both times. If the
user attempts to access a character at a location
beyond the end of the array, the last character—that
is, len-1—is returned.
Lines
117-127 implement operator plus (+) as a concatenation
operator. It is convenient to be able to write
String3
= String1 + String2;
and
have String3 be the concatenation of the other two
strings. To accomplish this, the operator plus function
computes the combined length of the two strings
and creates a temporary string temp. This invokes
the private constructor, which takes an integer,
and creates a string filled with nulls. The nulls
are then replaced by the contents of the two strings.
The left-hand side string (*this) is copied first,
followed by the right-hand side string (rhs).
The
first for loop counts through the string on the
left-hand side and adds each character to the new
string. The second for loop counts through the right-hand
side. Note that i continues to count the place for
the new string, even as j counts into the rhs string.
Operator
plus returns the temp string by value, which is
assigned to the string on the left-hand side of
the assignment (string1). Operator += operates on
the existing string—that is, the left-hand side
of the statement string1 += string2. It works just
like operator plus, except that the temp value is
assigned to the current string (*this = temp) in
line 140.
The
main()function (lines 143-173) acts as a test driver
program for this class. Line 145 creates a String
object by using the constructor that takes a null-terminated
C-style string. Line 146 prints its contents by
using the accessor function GetString(). Line 148
creates another C-style string. Line 149 tests the
assignment operator, and line 150 prints the results.
Line
152 creates a third C-style string, tempTwo. Line
153 invokes strcpy to fill the buffer with the characters
; nice to be here! Line 154 invokes operator +=
and concatenates tempTwo onto the existing string
s1. Line 156 prints the results.
In
line 158, the fifth character in s1 is accessed
and printed. It is assigned a new value in line
159. This invokes the nonconstant offset operator
([ ]). Line 160 prints the result, which shows that
the actual value has, in fact, been changed.
Line
162 attempts to access a character beyond the end
of the array. The last character of the array is
returned, as designed.
Lines
164-165 create two more String objects, and line
166 calls the addition operator. Line 167 prints
the results.
Line
169 creates a new String object, s4. Line 170 invokes
the assignment operator. Line 171 prints the results.
You might be thinking, "The assignment operator
is defined to take a constant String reference in
line 21, but here the program passes in a C-style
string. Why is this legal?"
The
answer is that the compiler expects a String, but
it is given a character array. Therefore, it checks
whether it can create a String from what it is given.
In line 12, you declared a constructor that creates
Strings from character arrays. The compiler creates
a temporary String from the character array and
passes it to the assignment operator. This is known
as implicit casting, or promotion. If you had not
declared—and provided the implementation for—the
constructor that takes a character array, this assignment
would have generated a compiler error.
|