| Reviewed in the LPString Example | Additional CString Review Concepts |
|---|---|
|
Throughout my first career as a software engineer, some of my tasks always involved writing and maintaining programs written in C. Midway through that career, I began writing and maintaining object-oriented programs written in C++. That experience allowed me to teach C++ to my colleagues within and without my main employment. At this time, experienced C programmers were the most frequent C++ students. So, creating a string class based on C-strings was a natural object-oriented programming example.
Creating a string class based on C-strings is still instructionally beneficial. It gives new C++ programmers additional experience using the C-string functions and demonstrates many object-oriented features presented in the current chapter. The previous example emphasized pictures for solving problems and converting the solutions to working C++ functions. This example encourages you to use the C-string functions' documentation and practice your elaboration skills. As you study each function, try to explain to yourself what each statement does. You can put yourself in an authentic mindset by imagining that you are in a formal code review and must explain to your peers how each function operates and why you have implemented it as it appears in the example. Working in small groups, taking turns elaborating successive functions to each other, is a proven learning technique.
As we transition our string class from length-prefixed to C-style strings, please consider the following issues:
Now we now have sufficient background to outline our class, which we name CString to emphasize its reliance on C-strings.
| CString Class | Member Variables |
|---|---|
class CString
{
private:
char* text = nullptr;
size_t capacity = 0;
}; |
|
| Empty String | String With Content |
![]() |
![]() |
Member functions that create new CString objects or modify this object must update both member variables.
#pragma once
#include <cstring> // Access the C-string functions
using namespace std;
class CString
{
private:
char* text = nullptr; A Cstring has two member variables: text and capacity
size_t capacity = 0;
public:
// constructors and destructor
CString(); // Default constructor
CString(size_t cap); // General (not conversion) constructor
CString(char c); // Conversion constructor
CString(const char* s); // Conversion constructor
CString(const CString& s); // Copy constructor
~CString(); // Destructor
// access functions - not all are named "get" or "set"
size_t length() const; // Number of saved characters
size_t get_capacity() const; // Total number of characters
char& at(int index); // Reference to 1 character: can be an l-value or r-value
// i/o functions
static void print(const char* line); // Print line to console
void print() const; // Print this string to console
void println() const; // Print this string to console with new-line
void readln(size_t n); // Read a line (max n characters) from console
// functions modifying "this"
void append(const CString& s); // Append s to the end of this
void insert(const CString& s, int index); // Insert s into this at index
void clear(); // Logically removes all characters from this string
// functions creating new CString objects
CString copy() const; // Make a copy of this string
CString concat(const CString& s) const; // Make a new string by joining this and s
CString substring(int index, size_t length) const; // Make a new string by extracting a substring from this
// ordering
bool equals(const CString& s) const; // Equal if & only if every character is the same
int order(const CString& s) const; // ASCIIbetically orders two strings
private:
void grow(size_t new_capacity); // A helper function that increases a string's capacity
};
inline CString::CString()
: capacity(15), text(new char[15])
{
text[0] = '\0';
}
|
inline CString::CString(size_t cap)
: capacity(cap), text(new char[cap])
{
text[0] = '\0';
}
|
inline CString::CString(char c)
{
capacity = 2;
text = new char[2];
text[0] = c;
text[1] = '\0';
} |
| (a) | (b) | (c) |
inline CString::CString(const char* s)
{
capacity = strlen(s) + 1;
text = new char[capacity];
strcpy(text, s);
} |
inline CString::CString(const CString& cs)
{
capacity = cs.capacity;
text = new char[capacity];
strcpy(text, cs.text);
} |
inline CString::~CString()
{
if (text != nullptr)
delete[] text;
}
|
| (d) | (e) | (f) |
inline functions in the class header file below the class specification. This figure and those following provide complete function implementations without elaborating how or what the individual function statements do.
CSstring cs(25); may be a call to (b) or to (c). Client code must disambiguate the call with a typecast: CString cs((size_t)25);. The C++ string class avoids the problem by not defining a similar constructor.inline size_t CString::length() const
{
return strlen(text);
} |
inline size_t CString::get_capacity() const
{
return capacity;
} |
| (a) | (b) |
char& CString::at(int index)
{
if (index < 0 || index > strlen(text) - 1)
throw "index out of bounds";
return text[index];
} |
|
| (c) |
cout << cs.at[4] << endl;cs.at[4] = 'X';inline void CString::print(const char* line)
{
cout << line;
} |
inline void CString::print() const
{
cout << text;
} |
| (a) | (b) |
inline void CString::println() const
{
print();
cout << '\n';
}
|
void CString::readln(size_t n)
{
if (n >= capacity)
grow(n + 1);
cin.getline(text, n);
} |
| (c) | (d) |
![]() |
void CString::readln()
{
const int NBLOCKS = 1024;
const int BLKSIZE = 512;
char** blocks = new char*[NBLOCKS];
int count = 0;
for (; count < NBLOCKS; count++)
{
blocks[count] = new char[BLKSIZE];
for (int i = 0; i < BLKSIZE - 1; i++)
{
int c = cin.get();
if (c != '\n' && c != EOF)
blocks[count][i] = c;
else
{
blocks[count][i] = '\0';
goto done;
}
}
blocks[count][BLKSIZE - 1] = '\0';
}
done:
delete[] text;
capacity = count * NBLOCKS + strlen(blocks[count]) + 1;
text = new char[capacity];
text[0] = '\0';
for (int i = 0; i <= count; i++)
{
strcat(text, blocks[i]);
delete[] blocks[i];
}
delete[] blocks;
} |
The amount of memory the operating system makes available to a program is the one constraint on a CString's capacity. A program can allocate what is needed with the new operator if there is sufficient memory. But the program doesn't "know" how much memory to allocate until the full CString contents are in memory. We solve the problem by allocating and filling blocks of memory as needed and assembling the final CString after the program reads all the text. The get function, first introduced in the wc.cpp example, reads characters from the console one at a time.
This solution still fails to read an arbitrarily long string. Nevertheless, we can adjust the symbolic constants to make the function read strings as long as we need. If we replace the blocks array with a linked list, the function can continue reading text until it exhausts its available memory. While this version is a good overall solution, it's a more appropriate topic for a Data Structures And Algorithms course.
Process functions perform general operations on an object's member variables. They include non-private member functions that don't fit the other labeled categories. See The UML Class Symbol.
Three CString functions deliver the results of their operations to the client program by updating this object - the object bound to the function at call time.
void CString::append(const CString& s)
{
if (strlen(text) + strlen(s.text) >= capacity)
grow(strlen(text) + strlen(s.text) + 1);
strcat(text, s.text);
} |
inline void CString::clear()
{
text[0] = '\0';
}
|
| (a) | (b) |
void CString::insert(const CString& s, int index) { if (index < 0 || index > strlen(text)) // validate index is inbounds throw "index location is out of bounds"; if (strlen(text) + strlen(s.text) >= capacity) // grow "this" string if needed grow(strlen(text) + strlen(s.text) + 1); memmove(&text[index + strlen(s.text)], &text[index], strlen(text) - index + 2); memcpy(&text[index], s.text, strlen(s.text)); }
strlen(text) - index + 1 is the number of characters the function moves or shifts, and we add one more to account for the null-termination character. Notice that memmove doesn't erase the characters in locations 6 through 9.The three functions in this category return a new CString representing the results of their operations. Each function defines a local CString object, named local, operates on it, and returns it when its work is complete.
CString CString::copy() const
{
CString local(capacity);
strcpy(local.text, text);
return local;
}
|
CString CString::concat(const CString& s) const { CString local(strlen(text) + strlen(s.text) + 1); strcpy(local.text, text); strcat(local.text, s.text); return local; } |
| (a) | (b) |
|
CString CString::substring(int index, size_t length) const
{
if (index < 0 || index > strlen(text))
throw "index location is too large";
if (index + length > strlen(text))
throw "\"length\" is too long";
CString local(length + 1);
strncpy(local.text, &text[index], length);
local.text[length] = '\0';
return local;
} |
| (a) | (b) |
The two functions in this category compare two CString objects: this and the parameter s.
bool CString::equals(const CString& s) const { return !strcmp(text, s.text); } |
int CString::order(const CString& s) const
{
return strcmp(text, s.text);
} |
| (a) | (b) |
!, converts to true.In chapter 6, I claimed that functions influence how software developers think about and solve problems. Further, developers decompose large complex functions into smaller, more manageable ones. These observations remain true for member functions. Developers often decompose large member functions into smaller helper functions and make them private because they help the other member functions rather than forming a complete service.
void CString::grow(size_t new_capacity)
{
char* temp = new char[new_capacity];
strcpy(temp, text);
delete[] text;
text = temp;
capacity = new_capacity;
}
|
void CString::set_capacity(size_t new_capacity)
{
char* temp = new char[new_capacity];
strncpy(temp, text, new_capacity - 1);
temp[new_capacity - 1] = '\0';
delete[] text;
text = temp;
capacity = new_capacity;
} |
| (a) | (b) |
private section to a public one| View1 | Download |
|---|---|
| CString.h | CString.h |
| CString.cpp | CString.cpp |
| client.cpp | client.cpp |