Question: Create a simple String class that represents a character string abstraction. The class must use the C++ new and delete operators to dynamically manage memory

Create a simple String class that represents a character string abstraction. The class must use the C++ new and delete operators to dynamically manage memory (in the heap) to store the character data. Provide the following member functions:

A constructor accepting a character pointer to an array of characters, with a default argument value of 0 (null pointer). If a null pointer or empty string (char array of length 1 containing a '\0' null byte) is passed, it initializes this String object to point at a dynamically allocated character array of length 1 (containing the null byte value '\0') in the heap. Otherwise, it dynamically allocates a char array of the same size as the parameter char array, and copies the parameter char data into it. Note that this is a deepcopy (ie. copying all the char data) so that this object has it's own copy of the char data, vs. a shallow copy (just copying the pointer value to the parameter's char data).

A copy constructor accepting a String object (by const ref). It initializes this String from the parameter String object, making a deep copy of the character data by dynamically allocating a char array of the same size as the parameter String's, and copying the char data.

A destructor that uses the C++ delete operator to free the dynamic character array owned by this String, and then sets the mpText pointer member to null. This prevents the dynamic char array from becoming a memory leak.

A length member function returning the current size of the character data (not including the final null byte).

An asChar member function returning a const char pointer to this String's encapsulated character data. It does not allocate new memory for the char data to be returned.

An assign member function accepting a String object parameter (by const ref). It first checks for assignment to self and returns if true. Otherwise, it uses delete to release the current char array it points to, then dynamcally allocates a new char array of the same size as the parameter String's, and copies the character data from the parameter String.

An assign member function accepting a character pointer to an array of characters. It checks for assignment to self, and returns if true. Otherwise, does the same as the other assign member function.

An equals member function accepting a String object parameter (by const ref), and a return type of bool. It compares the character data of this String with the character data of the parameter String object and returns true if they match, else false (the C++ string library function strcmp or strncmp can be used for the comparison).

An indexOf member function accepting a character parameter, and a return type of int. It searches thisString's character data for the first occurrence of the parameter character and returns the position if found (first character is position 0) or -1 if not found.

Note that the C++ Standard Library string class (std::string) is not used in this assignment and should not be included in any of the code.

Implementation Notes

Use the following code as the basis for your String class (the class name TString is used to prevent conflicts with any other String class). Put the complete class definition in a header file (TString.h) as follows:

// -- TString.h header file -- #ifndef _TSTRING_H // only include once in a compilation unit #define _TSTRING_H class TString { // Prefix with 'T' for uniqueness public: TString(const char *pText = 0); // default ctor // TODO: -- add remaining member function declarations here -- private: int mLength; // length of char data (not including null byte) char *mpText; // pointer to dynamic char array in heap }; #endif // _TSTRING_H 

Place all the member function implementation code in a separate source file (TString.cpp). It requires a #include "TString.h" at the beginning of the file to include the class definition (above):

// -- TString.cpp source file -- #include "TString.h" TString::TString(const char *pText) { .... } // TODO: -- Add remaining TString class member functions -- ... 

Use the C++ operators new and delete .in the TString class member functions to dynamically allocate and free the heap memory used to store the character data. Do not usemalloc() and free().

TString parameter(s) in member functions should be defined as const reference parameters to (1) indicate the member function does not change the parameter, (2) improve performance since pass-by-ref doesn't make a copy of the caller's argument object, and (3) allow const objects to be used as arguments when calling the function.

TString member functions that do not change the state of 'this' object should be specified as const member functions to allow them to be invoked on const objects.

The class member mpText must always be assigned a pointer value obtained by calling the C++ new operator to allocate a character array in the heap, and never set to point at a non-heap address, e.g:

mpText = ""; // error - points to non-heap memory

The problem occurs when the C++ delete operator is used to free the character array pointed to by mpText. If mpText points to memory not obtained by calling the C++ newoperator, memory corruption can occur, causing a program crash (possibly at a later point in the program, very hard to find)..

mpText must always point to a correctly sized dynamic character array, i.e., the class cannot just allocate a large character array (e.g. 500) and assume any text assigned will fit in that amount of space. Each time a TString object is created, assigned to, etc., the memory allocated for the character array must be the correct size for the text to be stored. Any existing dynamic memory must be released using delete to prevent memory leaks.

The assign member function should check for assignment-to-self, and if so, simply return. Otherwise, it should acquire heap memory for the new character array prior to releasing the heap memory it already owns.

Note that member functions with const TString& parameters can be called with a char pointer argument due to C++ implicit conversion rules. C++ can silently call a single argument constructor to convert one type to another where needed. For example, if the equals() member function is called with a char* argument, C++ will silently invoke theTString(const char*) constructor to create a temporary TString object to pass as the argument. Thus, you will see an extra (unexpected) constructor call in this situation.

All member functions with character pointer parameters must test for a null pointer, and treat it as an empty (length 0) string (i.e: ""). Do not pass null pointers to the C++ string library functions (e.g: strlen), as it is not portable code (will not work on all platforms).

All member functions must manage heap memory correctly, and must not leak heap memory (i.e. they must delete any heap memory no longer used so it can be recycled).

The C++ string library functions (e.g: strlen, strcpy_s, strcat_s, strcmp, stricmp, etc. ) may be used to implement the TString class member functions (e.g. use strcpy_s for copying char data instead of writing a for-loop).

The C++ Standard Library string class (std::string) is not used in this assignment and should not be included in any of the code.

A few notes on pointers, arrays, and characters in C++

A null pointer typically contains 0 (NULL) and should never be de-referenced, nor passed to the C++ string library functions (e.g. strlen, strcat, strcmp, etc).

Neither C nor C++ have a built-in string type, only arrays of characters. By convention, C/C++ character arrays representing a "string" end in a terminating null byte ('\0'). The length returned by strlen() doesn't include the terminating null byte.

A double quoted set of characters ("hello world") is compiled as an unnamed array of characters with a terminating null byte, and placed in the initialized data area of the executable.

"x" is different than 'x'. The double-quoted "x" is a null terminated character array (2 chars required), whereas the single-quoted 'x' is a single character.

An octal escape sequence can be used to specify a octal character value (e.g: '\0' for a null byte containing 8 zero bits). Certain control characters can be specified with a character escape sequence (e.g: ' ' for newline, '\t' for tab, etc), which C++ converts to the correct character value.

When passing an array to a function in C/C++, a pointer to the first element is passed (arrays are not passed by value, but by reference).

p = ""; assigns the address of an empty (length 0) string literal (character array of size [1] with the value '\0') in the initialized data area of memory to the character pointer p. p="hello"; assigns p the address of the first character of a six character array. p = 'x'; sets the pointer to the binary value of the character 'x'.

If p and q both point at null terminated character arrays, p == q does not compare their character data values, only the pointer values. To compare the character data, use the C library strcmp_s() function, or an explicit loop that compares individual characters as *p == *q.

A character pointer can be indexed to access elements of the character array, e.g: p[n] addresses the n'th character in the array of characters pointed to by p. So doesp+n

The Visual Studio debugger enables you to see pointer values (via locals), values in dynamic memory pointed to (via memory window), etc. Very helpful in debugging code working with dynamic memory.

Testing

Use the attached XString2.cpp source file to test your class and submit the test output.

Note that to develop your TString class, you may want to write your own test main() and focus on implementing one member function of TString at a time. For example, start with the char* constructor, then length(), asChar(), and so on. Once you've implemented and tested each of the member functions, then run the attached test code. It may find additional issues in your TString class implementation (for example, missing 'const' on some member functions, etc).

//----------------------------------------------------------------------------- // XString2.cpp - Test program for simple String class //-----------------------------------------------------------------------------

/* * Note: If using MS VC++, you will create this project with multiple files. * TString.h, TString.cpp, and this file (XString2.cpp) that contains main(). * * Click File->New, select the Project tab, and "Win32 console application" * to create the new project. Select the location for the project, * enter the project name (eg. Homework3), and Click OK. On the next window, * select "Empty Project", and click Finish. * * Click on the "FileView" tab in the left window to see the Source, Header, * and Resource files in your project (none to start). * * Click File->New, "C/C++ Header File" to create your TString.h header file. * This file will contain your class definition, which is included into any * file that will use your TString class. * * Click File->New, "C++ Source File" to create your TString.cpp source file. * This file will contain the member functions implementing your TString class. * It must #include "TString.h" to have the compiler see the header when * compiling it. * * Copy this file (XString2.cpp) into your project directory. * * Click Project->Add To Project->Files, select this file (XString2.cpp) from * the browse window, and click OK. * * Your project should now contain the Source files TString.cpp and XString2.cpp, * and the Header files TString.h * * Write the TString class definition in the TString.h file, write your * implementation code in TString.cpp, compile, and test with the main() in * XString2.cpp. * ****************************************************************************** * Note - although this code will test many aspects of your TString class, * having it run successfully doesn't guarantee that there are no runtime * dynamic memory logic errors in your code. * */

#include "TString.h"

#include #include #include using std::ostream; using std::ofstream; using std::cin; using std::cout; using std::dec; using std::endl; using std::flush; using std::hex; using std::ios;

// Note - If you are not using Microsoft Visual Studio, you may need to uncomment the following // code for this test program to compile. strcpy_s is Microsoft's secure replacement for strcpy // and may not exist in other development environments. /* #include #define strcpy_s(a, b, c) strncpy(a, c, b) */

const char *pOutfileName = "XStringOut.txt";

const char *pHello = "Hello"; const char *pHelloWorld = "Hello world from C++"; const char *pHelloLower = "hello world from c++"; const char *pHiMom = "Hi Mom!"; const char *pHi = "Hi "; const char *pHo = "Ho "; const char *pMom = "Mom!"; const char *pWelcome = "Welcome!"; const char *emptyString = "";

const int tempBufLen = 200; char tempBuf[tempBufLen];

const char* copyToTemp(const char* pText) { strcpy_s(tempBuf, tempBufLen, pText); return tempBuf; }

void clearTemp() { strcpy_s(tempBuf, tempBufLen, "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"); }

// note - you may need to change the definition of the main function to // be consistent with what your C++ compiler expects. int main() { ofstream outfile (pOutfileName, ios::out); if( !outfile.is_open() ) { cout << "Error - unable to open output file: " << pOutfileName << endl; return 1; }

//cout << "Writing output to " << pOutfileName << "... please wait" << endl; //ostream& outstream = outfile; // uncomment to write to file ostream& outstream = cout; // uncomment to write directly to cout

const char *mp = 0; int step = 0; outstream << "----- simple String class test ----- (02/01/2013)" << endl << endl;

// const checks (compile tests) // If there are compile errors in this section, then there are probably const specifiers missing from your // class definition, e.g. the length() member function should be defined as a const member function so // it can be invoked on a const TString object, as should all other member functions that don't change the // state of 'this'. { if (0 != 0) // only a compilation check for these methods at this point (for const). Tested further below. { const TString s0(pHello); // create a const TString object int n = s0.length(); //bool f = s0.equalsIgnoreCase(pHello); bool f = s0.equals(pHello); const char *p = s0.asChar(); n = s0.indexOf('h'); const TString s1(s0); // const String object parameter

TString s2; s2.assign(s0); // const String object parameter } }

// Default ctor outstream << " ----- Step " << ++step << " - default ctor -----" << endl; { TString s0; // default arg value outstream << "s0 using default ctor with default arg = \"" << s0.asChar() << "\" (length = " << s0.length() << ")" << endl; mp = (0 == s0.length()) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s0.length() << endl; mp = (0 != s0.asChar()) ? "OK: " : "ERROR: "; outstream << mp << "asChar() return value " << hex << "0x" << reinterpret_cast(s0.asChar()) << dec << endl; mp = ('\0' == *(s0.asChar())) ? "OK: null byte " : "ERROR: null byte not "; outstream << mp << "at position 0" << endl; mp = ("" != s0.asChar()) ? "OK: doesn't point " : "ERROR: points at "; outstream << mp << "at static literal \"\"" << endl; } { TString s0(copyToTemp(pHello)); // char * parameter clearTemp(); outstream << endl << "s0 using default ctor with char* parameter = \"" << s0.asChar() << "\" (length = " << s0.length() << ")" << endl; mp = (s0.length() == strlen(pHello)) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s0.length() << endl; mp = (0 == strcmp(s0.asChar(), pHello)) ? "OK: " : "ERROR: "; outstream << mp << "value is \"" << s0.asChar() << "\"" << endl; mp = (s0.asChar() != pHello) ? "OK: different " : "ERROR: same "; outstream << mp << "pointer value returned from asChar()" << endl; mp = (s0.asChar() == s0.asChar()) ? "OK: same " : "ERROR: different "; outstream << mp << "pointer value returned for successive calls of asChar()" << endl; }

// Copy ctor outstream << " ----- Step " << ++step << " - copy ctor -----" << endl; { TString s0(copyToTemp(pHiMom)); clearTemp(); TString s1(s0); // copy ctor outstream << "s1 using copy ctor = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (s1.length() == s0.length() && s1.length() == strlen(pHiMom)) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s1.length() << endl; mp = (s1.asChar() != s0.asChar()) ? "OK: different " : "ERROR: same "; outstream << mp << "pointer value returned from asChar()" << endl; mp = (0 == strcmp(s1.asChar(), pHiMom)) ? "OK: " : "ERROR: "; outstream << mp << "value is \"" << s1.asChar() << "\"" << endl; } { // Check copy of empty string TString s0; TString s1(s0); // copy ctor outstream << "s1 using copy ctor = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (0 == s1.length()) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s1.length() << endl; mp = (0 != s1.asChar()) ? "OK: " : "ERROR: "; outstream << mp << "asChar() return value " << hex << "0x" << reinterpret_cast(s1.asChar()) << dec << endl; mp = (s1.asChar() != s0.asChar()) ? "OK: different " : "ERROR: same "; outstream << mp << "pointer value returned from asChar()" << endl; mp = ('\0' == *(s0.asChar())) ? "OK: null byte " : "ERROR: null byte not "; outstream << mp << "at position 0" << endl; } { TString s0(copyToTemp("")); clearTemp(); TString s1(s0); // copy ctor outstream << "s1 using copy ctor = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (0 == s1.length()) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s1.length() << endl; mp = (0 != s1.asChar()) ? "OK: " : "ERROR: "; outstream << mp << "asChar() return value " << hex << "0x" << reinterpret_cast(s1.asChar()) << dec << endl; mp = (s1.asChar() != s0.asChar()) ? "OK: different " : "ERROR: same "; outstream << mp << "pointer value returned from asChar()" << endl; mp = ('\0' == *(s0.asChar())) ? "OK: null byte " : "ERROR: null byte not "; outstream << mp << "at position 0" << endl; }

// assign outstream << " ----- Step " << ++step << " - assign -----" << endl; { TString s0; s0.assign(copyToTemp(pHello)); // assign with char* parameter clearTemp(); outstream << "s0 using assign with char* parameter = \"" << s0.asChar() << "\" (length = " << s0.length() << ")" << endl; mp = (s0.length() == strlen(pHello)) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s0.length() << endl; mp = (0 == strcmp(s0.asChar(), pHello)) ? "OK: " : "ERROR: "; outstream << mp << "value is \"" << s0.asChar() << "\"" << endl; mp = (s0.asChar() != pHello) ? "OK: different " : "ERROR: same "; outstream << mp << "pointer value returned from asChar()" << endl; } { TString s0(copyToTemp(pHiMom)); clearTemp(); TString s1; s1.assign(s0); // assign with String parameter outstream << endl << "s1 using assign with String parameter = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (s1.length() == s0.length() && s1.length() == strlen(pHiMom)) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s1.length() << endl; mp = (0 == strcmp(s1.asChar(), pHiMom)) ? "OK: " : "ERROR: "; outstream << mp << "value is \"" << s1.asChar() << "\"" << endl; mp = (s1.asChar() != s0.asChar()) ? "OK: different " : "ERROR: same "; outstream << mp << "pointer value returned from asChar()" << endl; } { TString s0(copyToTemp(pHelloWorld)); clearTemp(); TString s1(copyToTemp(pHi)); clearTemp(); s1.assign(s0); // assign with String parameter outstream << endl << "s1 using assign with String parameter = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (s1.length() == s0.length() && s1.length() == strlen(pHelloWorld)) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s1.length() << endl; mp = (0 == strcmp(s1.asChar(), pHelloWorld)) ? "OK: " : "ERROR: "; outstream << mp << "value is \"" << s1.asChar() << "\"" << endl; mp = (s1.asChar() != s0.asChar()) ? "OK: different " : "ERROR: same "; outstream << mp << "pointer value returned from asChar()" << endl; } { TString s1(copyToTemp(pHello)); clearTemp(); outstream << endl << "s1 assign to self" << endl; const char* p1 = s1.asChar(); s1.assign(s1); // assign object to self mp = (s1.asChar() == p1) ? "OK: same " : "ERROR: different "; outstream << mp << " asChar() return value after String assign to self" << endl; p1 = s1.asChar(); s1.assign(s1.asChar()); // assign char* array to self mp = (s1.asChar() == p1) ? "OK: same " : "ERROR: different "; outstream << mp << " asChar() return value after asChar() assign to self" << endl; } { TString s1(copyToTemp(pHello)); clearTemp(); outstream << endl << "s1 assign with char* parameter" << endl; s1.assign(copyToTemp(pHi)); clearTemp(); outstream << "s1 = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (s1.equals(pHi)) ? "OK: matches " : "ERROR: doesn't match "; outstream << mp << "\"" << pHi << "\"" << endl; mp = (!s1.equals(emptyString)) ? "OK: doesn't match " : "ERROR: matches "; outstream << mp << "\"" << emptyString << "\"" << endl; }

// Test null pointer and null string args outstream << " ----- Step " << ++step << " - Null ptr and empty string -----" << endl; { TString s1(copyToTemp("not empty")); clearTemp(); outstream << endl << "Assigning empty string to s1 = \"" << s1.asChar() << "\"" << endl; s1.assign(emptyString); outstream << "s1 = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (s1.equals("")) ? "OK: matches " : "ERROR: doesn't match "; outstream << mp << "\"" << emptyString << "\"" << endl; mp = (0 == s1.length()) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s1.length() << endl; mp = (0 != s1.asChar()) ? "OK: " : "ERROR: "; outstream << mp << "asChar() return value " << hex << "0x" << reinterpret_cast(s1.asChar()) << dec << endl; } { TString s1(copyToTemp("still not empty")); clearTemp(); outstream << endl << "Assigning null pointer to s1 = \"" << s1.asChar() << "\"" << endl; s1.assign(static_cast(0)); outstream << "s1 = \"" << s1.asChar() << "\" (length = " << s1.length() << ")" << endl; mp = (s1.equals("")) ? "OK: matches " : "ERROR: doesn't match "; outstream << mp << "\"" << emptyString << "\"" << endl; mp = (0 == s1.length()) ? "OK: " : "ERROR: "; outstream << mp << "length = " << s1.length() << endl; mp = (0 != s1.asChar()) ? "OK: " : "ERROR: "; outstream << mp << "asChar() return value " << hex << "0x" << reinterpret_cast(s1.asChar()) << dec << endl; }

// indexOf outstream << " ----- Step " << ++step << " - indexOf -----" << endl; { TString s0(copyToTemp("Hi and hello")); clearTemp(); int pos = s0.indexOf('H'); mp = (0 == pos) ? "OK: 'H' found " : "ERROR: 'H' not found "; outstream << mp << " - indexOf = " << pos << endl; pos = s0.indexOf('h'); mp = (7 == pos) ? "OK: 'h' found " : "ERROR: 'h' not found "; outstream << mp << " - indexOf = " << pos << endl; pos = s0.indexOf('o'); mp = (11 == pos) ? "OK: 'o' found " : "ERROR: 'o' not found "; outstream << mp << " - indexOf = " << pos << endl; pos = s0.indexOf('z'); mp = (-1 == pos) ? "OK: 'z' not found " : "ERROR: 'z' should return -1 "; outstream << mp << " - indexOf = " << pos << endl; }

// Test many dynamic operations (this may take some time). // If the program aborts in this section, look at the code in the String ctors, // dtor, copy ctor, and assignment method to verify that dynamic memory // is managed correctly. outstream << " ----- Step " << ++step << " - Heap memory use -----" << endl; outstream << "Heap use test (this could take a minute or two): " << endl; { TString s2; TString t1; for (int iter = 0; iter < 50000; ++iter) { s2.assign(emptyString); TString t1(s2); for (int nc = 0; nc < 200; ++nc) { s2.assign(copyToTemp("0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999")); clearTemp(); s2.length(); s2.asChar(); t1.assign(s2); clearTemp(); s2.assign(copyToTemp("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); clearTemp(); t1.assign(s2); clearTemp(); t1.length(); t1.asChar(); s2.assign(copyToTemp("")); clearTemp(); s2.asChar(); t1.assign(static_cast(0)); t1.asChar(); } TString t2; t2.assign(s2); t2.assign(copyToTemp("xyzzy")); clearTemp(); t2.assign(t1); t2.assign(t2); t2.assign(static_cast(0)); t2.asChar(); if (iter%100 == 0) { outstream << "." << flush; } } outstream << "completed" << endl; }

// Testing dynamically allocated String objects. Uses the -> member selection operator for a // pointer to an object. outstream << " ----- Step " << ++step << " - Heap Strings -----" << endl; { TString *sp = new TString; // Create a heap based String object (sp points to it) sp->assign(copyToTemp(pHi)); clearTemp(); outstream << "sp = \"" << sp->asChar() << "\" (length = " << sp->length() << ")" << endl; mp = (sp->equals(pHi)) ? "OK: matches " : "ERROR: doesn't match "; outstream << mp << "\"" << pHi << "\"" << endl; mp = (!sp->equals(emptyString)) ? "OK: doesn't match " : "ERROR: matches "; outstream << mp << "\"" << emptyString << "\"" << endl; outstream << sp->asChar() << endl;

const char* cp = sp->asChar(); // ptr to char array int cpLen = sp->length(); delete sp; // Free the heap based String object. }

outstream << endl << " Test of simple String class completed" << endl << endl;

outfile.close(); return 0; }

Step by Step Solution

There are 3 Steps involved in it

1 Expert Approved Answer
Step: 1 Unlock blur-text-image
Question Has Been Solved by an Expert!

Get step-by-step solutions from verified subject matter experts

Step: 2 Unlock
Step: 3 Unlock

Students Have Also Explored These Related Databases Questions!