C++

String Classes

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.

Back to Index