User Defined String Class

Introduction

Most c++ compilers have one or two string classes available for programmers, but there is still a need to understand how to build your own string class. Many of the string classes will work with one compiler and not another, so companies develop their own class based on the primitive char* from c. This is highly transportable (even to different platforms -Unix for example) and there are no copyright infringement problems.

The Problem

The primitive c string type uses pointers to a char to store data. C pointer types can be viewed as arrays so if you create char *x, and allocate memory to x, then you can access x[i] a single character. The end of each c string is marked by the null character ('\0'), so if you expect to store at most 10 characters in a string, you must allocate space for 11 characters. The string functions all begin with the letters str (strcpy (copy), strcat (conatenate), strlen(length) and strcmp(compare) are a few of the functions available).

You will create a class that wraps the char* and makes sure there are no memory leaks. Your strings will also grow (or shrink) so that you do not need to worry about the maximum size of a string before you write a program.You will provide concatenate operator + as well as I/O stream operators << and >>. One function shared by most of the string classes is c_str that returns a char*. Another often used function is length. In addition you will need to make sure that the copy constructor and assignment operator provide deep copies.

The Implementation

The Class

The basic string class is defined as MyString (not the greatest name) since you do not want ambiguous references to the string classes packaged with various compilers. This is a mininimal string class that could be extended by functions for finding particular characters or substrings in a string and various other similar functions. You should avoid overloading the [] operator since you want your string to act as a unit not an array of characters. Notice that you will have to create the copy constructor, destructor and assignment operator since you will need deep copies of objects in this class.

class  MyString
{
public :
   MyString(char  *x =0);
   MyString(const  MyString &);
   ~MyString();
   MyString& operator  =(const  MyString&);
   int  operator  ==(const  MyString&);
   int  operator  !=(const  MyString&);
   MyString operator  +=(const  MyString&);
   char * c_str()const ;
   int  length();
   MyString readLine(istream&);//read to end of line  
   istream& read(istream&); //read to white space  
   ostream& write(ostream &)const ;
private :
   char  *_data;
   int  _length;
};

The read, write and += functions are used to implement the binarary operators (listed below). The read function reads characters up to the first space whereas the readLine function reads an entire line. If the line is blank the readLine function trys to read the next line.

Binary Operators

The string class should have a concatentate operator, and traditionally that operator is +. It is a binary operator that could be placed in the string class, but if you do that, the first string in the operation is modified by the operation. That is if you execute the following steps:

	MyString s = "The end ";
	MyString t = "of time";
	MyString r = s + t;

The nasty side effect is that s is changed to "The end of time". Since this is not usually desirable, you do not want the + to be part of the class MyString. You will notice that += defined in the class is the concatenate onto (similar to add to for the int type) operation that has the effect of adding a string to the end of the current string.

To avoid this problem and the problem of how to handle the experssion "a" + s, you will build your concatenate operator (and the stream operators) as standalone operators. These operators are declared after the class definition for MyString.

MyString operator + (const MyString&,const MyString&);

ostream& operator << (ostream &out,const MyString &s);

istream& operator >> (istream&, MyString&);

The Implementation

Memory Management

The memory management functions (copy constructor, destructor and assignment operator) are given here. Notice that the copy constructor and the assignment operator are almost identical, except that you must delete the existing data in the assignment operator.Not that the existing data is deleted only after the new concatenated string has been created in a temporary pointer. This is to make sure that the statement s = s + t; is handled properly. Also not that your should check to see that the pointer _data is not NULL before you delete it.

MyString::MyString(const  MyString &x)
{
   if (x._length >0)
   {
      _length = x._length;
      _data = new  char[_length+1];
      strcpy(_data,x._data);
   }
   else 
   {
      _length=0;
      _data = NULL;
   }
}
MyString::~MyString()
{
   if (_data)
      delete  [] _data;
}

MyString& MyString::operator  =(const  MyString &x)
{
   if (x._length >0)
   {
      _length = x._length;
      char  *temp = new  char[_length+1];
      strcat(temp,x._data);
      if (_data)
         delete  [] _data;
      _data = temp;
   }
   else 
   {
      _length=0;
      if (_data)
         delete  [] _data;
      _data = NULL;
   }
   return  *this;
}

Concatenate

The main issue in the concatenate onto operator (+=) is that you have to allocate enough memory for a new string containing both of the strings involved in the concatenation. A temporary location is created to hold the current string and after the new string is created, the temporary string (old value of the string) is deleted.

MyString MyString::operator  +=(const  MyString &s)
{
   _length += s._length;
   char  *temp = _data;
   _data = new  char[_length +1];
   strcpy(_data,temp);
   strcat(_data,s._data);
   delete  [] temp;
   return  *this;
}

Friends

Many books (and some of Borlands help) suggest that when you create standalone operators like our + >> and << operators they should be declared as friends. If a non-class function is declared a friend it has access to all of the private variables in the class and this makes it easier to write the operators. The problem with this is that if you have a hierarchy, operators for the children must also be declared as friends or they will not be able to read values stored in the parent class. This creates many more problems than it solves. It is simpler to create public class functions that are called from the non-class functions (operators). These functions can also be called from the operators designed to work on children. In your example, the operators call on read, write and += to complete their tasks.

MyString operator  + (const  MyString &s1,const  MyString &s2)
{
   MyString c = s1;
   c += s2;
   return  c;
}

ostream& operator <<(ostream &out,const  MyString &s)
{
   return  s.write(out);
}

istream& operator >> (istream &in, MyString &s)
{
   return  s.read(in);
}

Complete Code

To get the complete code for this class and a test program click here .