I wanted to make a feature rich string in C++. Similar to one in python. I am using C++14.
I have not added much yet, I just want to get my code reviewed to ensure I am going the right way.
Features:
- Negative indexing.(
.at(-i)
) (Slices coming soon) - Mutable string, no fixed length.
- Can delete according to index.(
.pop(i)
) - Justify text. (
.rjust
,.ljust
,center
)
Questions
- Am I using
new
anddelete
properly? - Is my class structure correct?
- Is my
TString
optimized well? - Am I missing any optimized library functions, but instead implementing myself?
tstring.h
#pragma once
#include <iostream>
#include <stdexcept>
char char_lower(const char);
char char_upper(const char);
class TString
{
private:
char* __list;
unsigned int __len;
public:
TString();
TString(const char);
TString(const char*, unsigned int);
TString(unsigned int, const char);
TString(const TString&);
~TString();
friend std::ostream& operator<<(std::ostream&, const TString&);
char& operator[](unsigned int) const;
bool operator==(const TString&) const;
bool operator!=(const TString&) const;
TString& operator=(const TString&);
TString& operator+=(const TString&);
TString& operator*=(unsigned int);
TString operator+(const TString&) const;
TString operator*(unsigned int) const;
friend TString operator+(const char, const TString&);
friend TString operator*(unsigned int, const TString&);
unsigned int length() const;
char& at(int) const;
char& begin() const;
char& end() const;
void pop(int index);
bool is_empty() const;
TString lower() const;
TString upper() const;
TString reverse() const;
TString ljust(unsigned int, const char) const;
TString center(unsigned int, const char) const;
TString rjust(unsigned int, const char) const;
bool contains(const char) const;
bool contains(const TString&) const;
int index(const char) const;
int index(const TString&) const;
unsigned int count(const char) const;
unsigned int count(const TString&) const;
};
str.cpp
#include "tstring.h"
char char_lower(const char char_)
{
switch (char_)
{
case 'A': return 'a';
case 'B': return 'b';
case 'C': return 'c';
case 'D': return 'd';
case 'E': return 'e';
case 'F': return 'f';
case 'G': return 'g';
case 'H': return 'h';
case 'I': return 'i';
case 'J': return 'j';
case 'K': return 'k';
case 'L': return 'l';
case 'M': return 'm';
case 'N': return 'n';
case 'O': return 'o';
case 'P': return 'p';
case 'Q': return 'q';
case 'R': return 'r';
case 'S': return 's';
case 'T': return 't';
case 'U': return 'u';
case 'V': return 'v';
case 'W': return 'w';
case 'X': return 'x';
case 'Y': return 'y';
case 'Z': return 'z';
default: return char_;
}
}
char char_upper(const char char_)
{
switch (char_)
{
case 'a': return 'A';
case 'b': return 'B';
case 'c': return 'C';
case 'd': return 'D';
case 'e': return 'E';
case 'f': return 'F';
case 'g': return 'G';
case 'h': return 'H';
case 'i': return 'I';
case 'j': return 'J';
case 'k': return 'K';
case 'l': return 'L';
case 'm': return 'M';
case 'n': return 'N';
case 'o': return 'O';
case 'p': return 'P';
case 'q': return 'Q';
case 'r': return 'R';
case 's': return 'S';
case 't': return 'T';
case 'u': return 'U';
case 'v': return 'V';
case 'w': return 'W';
case 'x': return 'X';
case 'y': return 'Y';
case 'z': return 'Z';
default: return char_;
}
}
TString::TString():
__list{new char[0]}, __len{0}
{};
TString::TString(const char char_):
__list{new char[1]}, __len{1}
{
__list[0] = char_;
}
TString::TString(const char* char_array, unsigned int len):
__list{new char[len]}, __len{len}
{
for (int i = 0; i < len; ++i)
{
__list[i] = char_array[i];
}
}
TString::TString(unsigned int len, const char char_):
__list{new char[len]}, __len{len}
{
for (int i = 0; i < len; ++i)
{
__list[i] = char_;
}
}
TString::TString(const TString& str):
__list{new char[str.length()]}, __len{str.length()}
{
for (int i = 0; i < str.length(); ++i)
{
__list[i] = str[i];
}
}
TString::~TString()
{
delete[] __list;
delete &__len;
}
std::ostream& operator<<(std::ostream& os, const TString& str)
{
for (int i = 0; i < str.length(); ++i)
{
os << str.__list[i];
}
return os;
}
char& TString::operator[](unsigned int index) const
{
return __list[index];
}
bool TString::operator==(const TString& str) const
{
if (__len != str.length())
{
return false;
}
for (int i = 0; i < str.length(); ++i)
{
if (__list[i] != str[i])
{
return false;
}
}
return true;
}
bool TString::operator!=(const TString& str) const
{
if (__len != str.length())
{
return true;
}
for (int i = 0; i < str.length(); ++i)
{
if (__list[i] != str[i])
{
return true;
}
}
return false;
}
TString& TString::operator=(const TString& str)
{
delete[] __list;
__len = str.length();
__list = new char[__len];
for (int i = 0; i < __len; ++i)
{
__list[i] = str[i];
}
return *this;
}
TString& TString::operator+=(const TString& str)
{
if (__len == 0)
{
*this = str;
} else {
char copy_list[__len];
int copy_len = __len;
for (int i = 0; i < copy_len; ++i)
{
copy_list[i] = __list[i];
}
delete[] __list;
__len += str.length();
__list = new char[__len];
for (int i = 0; i < copy_len; ++i)
{
__list[i] = copy_list[i];
}
for (int i = 0; i < str.length(); ++i)
{
__list[copy_len + i] = str[i];
}
}
return *this;
}
TString& TString::operator*=(unsigned int x)
{
TString result(*this);
if (__len == 0)
{
return *this;
}
for (int i = 1; i < x; ++i)
{
*this += *this;
}
return *this;
}
TString TString::operator+(const TString& str) const
{
TString result(*this);
result += str;
return result;
}
TString TString::operator*(unsigned int x) const
{
TString result(*this);
result *= x;
return result;
}
TString operator+(const char char_, const TString& str)
{
TString result(char_);
result += str;
return result;
}
TString operator*(unsigned int x, const TString& str)
{
return (str * x);
}
TString operator""_t(const char array[], unsigned int len)
{
return TString(array, len);
}
TString operator""_t(const char char_)
{
return TString(char_);
}
unsigned int TString::length() const
{
return __len;
}
char& TString::at(int index) const
{
if (index >= 0 && index < __len)
{
return (*this)[index];
} else if (index < 0 && index >= -__len) {
return (*this)[index + __len];
}
throw std::out_of_range("Index out of range!");
}
char& TString::begin() const
{
if (__len == 0)
{
throw std::domain_error("String is empty!");
}
return (*this)[0];
}
char& TString::end() const
{
if (__len == 0)
{
throw std::domain_error("String is empty!");
}
return (*this)[__len - 1];
}
void TString::pop(int index)
{
if (index < 0)
{
index += __len;
}
char* new_list = new char[__len - 1];
for (int i = 0; i < index; ++i)
{
new_list[i] = __list[i];
}
for (int i = index + 1; i < __len; ++i)
{
new_list[i - 1] = __list[i];
}
delete[] __list;
__list = new_list;
}
bool TString::is_empty() const
{
return (__len == 0);
}
TString TString::upper() const
{
TString result(*this);
for (int i = 0; i < __len; ++i)
{
result[i] = char_upper(__list[i]);
}
return result;
}
TString TString::lower() const
{
TString result(*this);
for (int i = 0; i < __len; ++i)
{
result[i] = char_lower(__list[i]);
}
return result;
}
TString TString::reverse() const
{
TString result;
for (int i = __len - 1; i >= 0; --i)
{
result += __list[i];
}
return result;
}
TString TString::ljust(unsigned int width, const char char_) const
{
if (width <= __len)
{
return *this;
}
int padding = width - __len;
return *this + TString(padding, char_);
}
TString TString::center(unsigned int width, const char char_) const
{
if (width <= __len)
return *this;
int total_padding = width - __len;
int rpad = total_padding / 2;
int lpad = total_padding - rpad;
return TString(rpad, char_) + *this + TString(lpad, char_);
}
TString TString::rjust(unsigned int width, const char char_) const
{
if (width <= __len)
return *this;
int padding = width - __len;
return TString(padding, char_) + *this;
}
bool TString::contains(const char char_) const
{
for (int i = 0; i < __len; ++i)
{
if (__list[i] == char_)
{
return true;
}
}
return false;
}
bool TString::contains(const TString& str) const
{
if (*this == str)
{
return true;
}
for (int i = 0; i < __len - str.length(); ++i)
{
for (int j = 0; j < str.length(); ++j)
{
if (__list[j + i] != str[j])
{
break;
} else if (j == str.length() - 1) {
return true;
}
}
}
return false;
}
int TString::index(const char char_) const
{
for (int i = 0; i < __len; ++i)
{
if (__list[i] == char_)
{
return i;
}
}
return -1;
}
int TString::index(const TString& str) const
{
if (*this == str)
{
return 0;
}
for (int i = 0; i < __len - str.length(); ++i)
{
for (int j = 0; j < str.length(); ++j)
{
if (__list[j + i] != str[j])
{
break;
} else if (j == str.length() - 1) {
return i;
}
}
}
return -1;
}
unsigned int TString::count(const char char_) const
{
unsigned int count = 0;
for (int i = 0; i < __len; ++i)
{
if (__list[i] == char_)
{
++count;
}
}
return count;
}
unsigned int TString::count(const TString& str) const
{
unsigned int count = 0;
if (*this == str)
{
++count;
return count;
}
for (int i = 0; i < __len - str.length(); ++i)
{
for (int j = 0; j < str.length(); ++j)
{
if (__list[j + i] != str[j])
{
break;
} else if (j == str.length() - 1) {
++count;
}
}
}
return count;
}
Basic use
int main()
{
TString hello = "Hello, World!"_t;
std::cout << hello << '\n';
}
Thanks!
delete &__len;
. In your basic usage,__len
will be on the stack and thusdelete
-ing it will be undefined behavior. \$\endgroup\$__len
did not come fromnew
, thus it should not bedelete
ed. The rule of thumb is that anything that came fromnew
ornew[]
must be matched bydelete
ordelete[]
respectively. Just remove that line and it should be fine. \$\endgroup\$std::vector<char>
when you could just build aroundstd::string
(which is essentially juststd::vector<char>
that is guaranteed to be NUL-terminated)? You don't need to re-implement the memory management thatstd::string
gives you; you're just trying to enhance the interface. As some of the answers note, current "best practices" in C++ is that these additional functions you want to add shouldn't be member functions of the class, but instead generic free functions. I have somewhat mixed opinions about that, but either way, always prefer composition when possible. \$\endgroup\$