A custom string class can be developed in C++ to mirror the functionality of the standard library's std::string. This implementation organizes data using a dynamically allocated character array, tracking length and capacity separately from the null terminator.
Core Data Structure
namespace custom {
class String {
public:
static const size_t npos = static_cast<size_t>(-1);
private:
char* data;
size_t len;
size_t cap;
};
}
The npos constant serves as a sentinel value for operations like find when no match is found. Member variables include a pointer to the character array, the current string length, and the allocated capacity.
Iterators
Iterators are implemented as pointers to characters, providing range-based access.
typedef char* iterator;
typedef const char* const_iterator;
iterator begin() { return data; }
iterator end() { return data + len; }
const_iterator begin() const { return data; }
const_iterator end() const { return data + len; }
Capacity Management
Basic functions return the current state of the string.
size_t size() const { return len; }
size_t capacity() const { return cap; }
void clear() { data[0] = '\0'; len = 0; }
bool empty() const { return len == 0; }
The reserve method increases capacity without modifying content, ensuring it never reduces capacity.
void reserve(size_t new_cap) {
if (new_cap > cap) {
char* new_data = new char[new_cap + 1];
std::strcpy(new_data, data);
delete[] data;
data = new_data;
cap = new_cap;
}
}
resize adjusts the string length, truncating or extending with a specified character, and may increase capacity if needed.
void resize(size_t new_len, char fill = '\0') {
if (new_len < len) {
data[new_len] = '\0';
len = new_len;
} else {
reserve(new_len);
std::memset(data + len, fill, new_len - len);
len = new_len;
data[len] = '\0';
}
}
Element Access
Subscript operators provide direct access to characters with bounds checking.
char& operator[](size_t idx) {
assert(idx < len);
return data[idx];
}
const char& operator[](size_t idx) const {
assert(idx < len);
return data[idx];
}
Modifications
Appending characters or strings handles reallocation automatically.
void push_back(char ch) {
if (len == cap) {
reserve(cap == 0 ? 5 : cap * 2);
}
data[len++] = ch;
data[len] = '\0';
}
String& append(const char* src) {
size_t src_len = std::strlen(src);
if (len + src_len > cap) {
reserve(len + src_len);
}
std::strcat(data, src);
len += src_len;
return *this;
}
Compound assignment operators leverage existing methods for simplicity.
String& operator+=(const char* src) {
return append(src);
}
String& operator+=(char ch) {
push_back(ch);
return *this;
}
Insertion at a position shifts existing characters to make room.
String& insert(size_t pos, const char* src) {
assert(pos <= len);
size_t src_len = std::strlen(src);
if (len + src_len > cap) {
reserve(len + src_len);
}
for (size_t i = len; i >= pos && i != npos; --i) {
data[i + src_len] = data[i];
}
std::memcpy(data + pos, src, src_len);
len += src_len;
return *this;
}
Deletion methods adjust the string length and null terminator placement.
void pop_back() {
if (len > 0) {
data[--len] = '\0';
}
}
String& erase(size_t pos, size_t count = npos) {
assert(pos < len);
if (count == npos || pos + count >= len) {
len = pos;
data[len] = '\0';
} else {
std::strcpy(data + pos, data + pos + count);
len -= count;
}
return *this;
}
Swapping exchanges internal pointers and metadata efficiently.
void swap(String& other) {
std::swap(data, other.data);
std::swap(len, other.len);
std::swap(cap, other.cap);
}
String Operations
Conversion to a C-style string returns the internal buffer.
const char* c_str() const {
return data;
}
Search functions locate characters or substrings, returning npos on failure.
size_t find(char ch, size_t start = 0) const {
for (size_t i = start; i < len; ++i) {
if (data[i] == ch) return i;
}
return npos;
}
size_t find(const char* substr, size_t start = 0) const {
const char* result = std::strstr(data + start, substr);
return result ? result - data : npos;
}
Substring extraction creates a new string from a portion of the original.
String substr(size_t start, size_t count = npos) const {
assert(start < len);
String result;
size_t copy_len = (count == npos || start + count > len) ? len - start : count;
result.reserve(copy_len);
for (size_t i = start; i < start + copy_len; ++i) {
result += data[i];
}
return result;
}
Constructors and Destructor
Constructors initialize the string from various sources, with a modern copy constructor using a temporary object.
String() : data(new char[1]), len(0), cap(0) {
data[0] = '\0';
}
String(const char* src) : len(std::strlen(src)), cap(len) {
data = new char[cap + 1];
std::strcpy(data, src);
}
String(const String& src) : data(nullptr), len(0), cap(0) {
String temp(src.data);
swap(temp);
}
The destructor releases allocated memory.
~String() {
delete[] data;
}
Assignment Operator
Assignment uses the copy-and-swap idiom for exception safety.
String& operator=(const String& src) {
if (this != &src) {
String temp(src);
swap(temp);
}
return *this;
}
Non-Member Functions
Stream operators enable input and output integration.
std::ostream& operator<<(std::ostream& out, const custom::String& str) {
for (char ch : str) out << ch;
return out;
}
std::istream& operator>>(std::istream& in, custom::String& str) {
str.clear();
char buffer[128];
size_t idx = 0;
char ch = in.get();
while (ch == ' ' || ch == '\n') ch = in.get();
while (ch != ' ' && ch != '\n') {
buffer[idx++] = ch;
if (idx == 127) {
buffer[idx] = '\0';
str += buffer;
idx = 0;
}
ch = in.get();
}
if (idx) {
buffer[idx] = '\0';
str += buffer;
}
return in;
}
A getline function reads entire lines, similar to the stream operator but ignoring space delimiters.
std::istream& getline(std::istream& in, custom::String& str) {
str.clear();
char buffer[128];
size_t idx = 0;
char ch = in.get();
while (ch != '\n') {
buffer[idx++] = ch;
if (idx == 127) {
buffer[idx] = '\0';
str += buffer;
idx = 0;
}
ch = in.get();
}
if (idx) {
buffer[idx] = '\0';
str += buffer;
}
return in;
}
Comparison operators define lexicographic ordering and equality.
bool operator==(const String& other) const {
return len == other.len && std::strcmp(data, other.data) == 0;
}
bool operator<(const String& other) const {
return std::strcmp(data, other.data) < 0;
}