char s[10]; strcpy(s, "How now brown cow?"); // Not enough room!
if (label1 > label2) // Can't do with arrays! label1 = label2;
| Problem | Solution using a String class |
|---|---|
| The error of accessing characters outside a string |
Make the String's data private and only allow
access to the characters of the string via a method that does
range-checking.
|
| The inefficient acquisition of a string's length | Store the length of the string as an additional piece of data in the String class. Update that field every time the contents of the string is altered. |
This time, we will augment our definition of the String class to address issues of static-ness, unnatural-ness and inconvenience.
char contents[MAX_STR_SIZE];
we will store them using a pointer:
char *contents;
and allocate space for the array it points to:
contents = new char[size];
With this design, we'll need to reallocate the array when its contents change. AND, we must remember to deallocate the memory once we are done with it.
char element(int i) const;
Usage:
if (str1.element(4) == 'a') ...
char operator [](int i) const;
Usage:
if (str1[4] == 'a') ...
int compare_to(const String &str) const;
Usage:
if (str1.compare_to(str2) >= 0) ...
bool operator ==(const String &str) const; bool operator !=(const String &str) const; bool operator >(const String &str) const; bool operator <(const String &str) const; bool operator >=(const String &str) const; bool operator <=(const String &str) const;
Usage:
if (str1 >= str2) ...
void assign(const char *s);
Usage:
str1.assign(s);
String &operator =(const String &str);
Usage:
str1 = str2;
so we should allow the same for objects.int a, b, c; ... (a = b) = c; // Assign b to a, then c to a.
By default, we can assign one object to another of the same type. Why then, do we need to write our own assignment operator?
The default behavior for assignment is to assign each data member from one object to another. For objects containing a pointer, that doesn't always work as desired:
| Code: |
String str1("a");
String str2("xy");
|
str1 = str2; |
|---|---|---|
| Memory: | str1 ------------ ------ |contents -+-->|a|\0| |----------| ------ |len 1| ------------ str2 ------------ -------- |contents -+-->|x|y|\0| |----------| -------- |len 2| ------------ |
str1
------------
|contents -+---+
|----------| |
|len 2| |
------------ |
|
str2 v
------------ --------
|contents -+-->|x|y|\0|
|----------| --------
|len 2|
------------
|
For Strings, the result after the default assignment operation is that
the contents data member
(the one that points to where the characters are stored)
points to the same location, so that if we change str1 or
str2 we end up affecting the other!
To avoid this, we must write our own operator =, which
does the right thing.
String str1; ... // = is initialization here, not assignment String str2 = str1, str3(str1);
void bar(String copy); ... String str1; bar(str1);
String biz()
{
String str;
...
return str;
}
Like assignment, the default behavior for copying is to copy the object by copying each data member. Since our String class contains a pointer, we'll have to write our own copy constructor:
String(const String &str);
works just fine, since no copy is made. Returning a String from a function works fine as well (see thevoid foo(String &str); // can change its String parameter void bar(const String &str); // just "uses" its String parameter
operator = we
will write).
String str1, str2; char s[100]; ... str1 = str2; // from a String object str2 = s; // from an array of characters if (str1 == str2 && str1 < s) ...
It would be convenient to have methods that can deal with both. Rather than writing one method for each, e.g.:
bool operator ==(const String &str); AND bool operator ==(const char *s);
we can simply write a constructor that converts an array of characters into a String object:
and then write all other methods in terms of String objects. Then, if we call a String method with an array of characters, the compiler will first call the conversion constructor to convert it to a String object, and then pass that temporary String object to the method:String(const char *s);
String str1;
char s[100];
...
if (str1 < s) // Converts 's' to String, then
// calls String::operator <
...
Your new String class will have the following design (changes or additions from the previous version are in red):
The String class code must be put in its own module, i.e., a file
mystring.h (the header file) should hold the
class definition and a file mystring.cpp (the
implementation file) should hold the method definitions.
Each String object should contain the following data as part of its internal representation:
The String class should provide the following methods for setting up, accessing and changing strings:
String();A default constructor that sets up the String to be an empty string.
String(const char *s);A conversion constructor that sets up the String with the value of the passed string.
String(const String &str);A copy constructor that sets up this String with the same value as another String.
This is needed when a String is initialized with another String, is passed by value or is returned from a function.
~String();A destructor that deallocates any memory that the String allocated.
String &operator =(const String &str); formerly void assign(const char *s);A method that copies the contents of the passed String into the String.
Don't forget to deal with the case of self-assignment.
void append(const String &str);A method that appends the contents of the passed String onto the end of the String the method is called on.
bool operator ==(const String &str) const; bool operator !=(const String &str) const; bool operator >(const String &str) const; bool operator <(const String &str) const; bool operator >=(const String &str) const; bool operator <=(const String &str) const; formerly int compare_to(const String &str) const;Methods that compare the String each method is called on with the String passed to the method. Each returns a true or false value based on whether the relationship (e.g.,
==, !=, >, <, >=, <=) holds or not.
void print() const;A method that prints the value of the String it is called on to
cout.
int length() const;A method that returns the length of the String that it is called on.
char operator [](int i) const; formerly char element(int i) const;A method that returns the ith element (zero-based, just like arrays) of the String it is called on. This method should print out an error message and return the nul character (
\0)
if the location asked for is out-of-range for the string.
string.h, which operate on arrays of
characters terminated by nul's (\0).
String-test2.cpp
provides a main program to test your new String class.
String str1;
String str2 = "abc", str3("def");
str1 = str2;
str3 = "value"; // More than one here!
cout << str1[3];
String str4 = str3;