Created
October 20, 2017 09:30
-
-
Save arhadthedev/b3f13592922587335899acb14795d9bb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _CRT_SECURE_NO_WARNINGS | |
#include <cstring> | |
#include <iostream> | |
// Забываем про using namespace std - сэкономит кучу нервов при появлении классов, | |
// одноимённых с таковыми из стандартной библиотеки | |
// Так как эти функция используется только здесь, объявил их как static (чтобы не были видна | |
// извне) | |
// | |
// Никогда, НИКОГДА не храните размер массива в знаковых целых. Для индексации есть | |
// специальный тип, size_t. | |
// | |
// И ещё, приручаемся ставить const всем указателям и переменным, которые не должны | |
// изменяться. Мало того, что это поможет отлавливать ошибки, так ещё мы получим возможность | |
// передавать строковые константы, которые имеют тип const char* | |
static size_t StrLen(const char* _str); | |
static void StrCpy(char* in_str, const char* src_str); | |
static char* StrCat(char*& str1, const char* str2); | |
class String | |
{ | |
public: | |
// Класс-обёртка над строкой, введённая для возможности определения своего operator+ | |
// | |
// Да-да, вся эта затея исключительно для того, чтобы перейти от неподконтрольного | |
// и вообще бессмысленного operator +(char*, char*) к нашему operator+ (Literal&, char*). | |
class Literal | |
{ | |
public: | |
explicit Literal(char** handledString); | |
void operator +(char *s); | |
// Для возможности неявного преобразования Literal в char*, чтобы Literal мог стоять | |
// по праую сторону знака приравнивания в строке: | |
// | |
// a[0] + a[1]; | |
// | |
// а также быть переданным cout-у, ожидающему именно указатель на строку. | |
operator char*() const; | |
// Для возможности присваивания строк Literal-у в выражениях вида: | |
// | |
// a[0] = "1"; | |
// | |
// ибо a[i] возвращает теперь Literal, о котором компилятор мало что знаетю | |
// | |
// Метод возвращает ссылку на Literal, чтобы можно было присваивать цепочкой: | |
// | |
// a[0] = a[1] = a[2] = "1"; | |
// | |
// Принимаемый указатель константен, потому что сам литералы (то, что в кавычках) | |
// константны. | |
Literal& operator=(const char* in); | |
private: | |
// Вообще-то мы работаем с просто указателем на строку, но так как нам надо | |
// её изменять, вводим второй уровень косвенности | |
char** str_; | |
}; | |
String(size_t); | |
String(const String&); | |
~String(); //Деструкторы принято помещать рядом с конструкторами, хотя это | |
//вопрос исключительно стиля | |
void showArray(); | |
// В оригинале этот метод зачем-то возвращал _ссылку_ на указатель. Зачем - не понял, | |
// но раз мы всё равно возвращаем обёртку, убрал. | |
Literal operator[](size_t j); | |
private: | |
// Опять int -> size_t | |
size_t sizeOfArray; | |
char **str; | |
}; | |
String::String(size_t size) | |
// Учимся пользоваться списками инициализации (заодно переупорядочил поля | |
// в объявлении класса, так как значение str зависит от значения sizeOfArray) | |
: sizeOfArray(size) | |
, str(new char *[sizeOfArray]) | |
{ | |
for (size_t i = 0; i < sizeOfArray; i++) | |
{ | |
str[i] = new char[2048]; | |
} | |
} | |
String::String(const String& obj) | |
: sizeOfArray(obj.sizeOfArray) | |
, str(new char *[sizeOfArray]) | |
{ | |
for (size_t i = 0; i < sizeOfArray; i++) | |
{ | |
str[i] = new char[2048]; | |
// Эта строка в оригинале: | |
// | |
// str[i] = obj.str[i]; | |
// | |
// не копировала символы, а делала второй указатель на исходную строку. Из-за | |
// этого уничтожение класса, откуда производилось копирование, уничтожало | |
// и этот общий буфер, приводя к скорой порче кучи. И ещё эта строка оставляла | |
// свежевыжевыделенный в предыдущей строке буфер "висячим", без указателей | |
// на него. | |
// | |
// Для посимвольного копирования и в Си, и в С++ есть специальная функция memcpy() | |
// (если вы не хотите полязоваться классом std::string, который сам делает всю эту | |
// работу). | |
// | |
// std::memcpy(str[i], obj.str[i], 2048 * sizeof(char)); | |
// str[2047] = '\0'; | |
// | |
// В отличие от strcpy() эта функция не сломается при отсутствии нуль-терминатора. | |
// И да, раз мы предоставляем посимвольный доступ к строковому массиву, стоит | |
// дополнительно удостовериться, что нуль-терминатор гарантированно есть. | |
// | |
// Но раз у вас уже есть StrCpy(), воспользуемся ей. Так как пример учебный, | |
// восстановление нуль-терминатора тоже пока опустим (хотя это делается в одну строку). | |
// | |
// И ещё, я поставил два двоеточия в начале вызова, чтобы указать компилятору, что эта | |
// функция должна быть обязательно вне классов и пространств имён. | |
::StrCpy(str[i], obj.str[i]); | |
} | |
} | |
String::~String() | |
{ | |
// В оригинале была единственная строка: | |
// | |
// delete str; | |
// | |
// что провоцировало утечку памяти. Да, при введении подобной очистки, действительно, ломается | |
// оригинальная логика копирования строковых указателей в String(const String& obj), | |
// но мы её уже починили. Зато сразу приручаемся писать классы, которые можно массово | |
// создавать и удалять, не опасаясь израсходования адресного пространства из-за утечек. | |
// | |
// И да, раз вы использовали new[], то и удалять надо парным оператором delete[]. Сейчас | |
// всё вроде бы всё и так работает, но с выходом какой-нибудь новой версии стандартной | |
// библиотеки всё может сломаться. | |
for (size_t i = 0; i < sizeOfArray; i++) | |
{ | |
delete[] str[i]; | |
} | |
delete[] str; | |
} | |
void String::showArray() | |
{ | |
for (size_t i = 0; i < sizeOfArray; i++) | |
{ | |
std::cout << str[i] << " | "; | |
} | |
std::cout << std::endl << std::endl; | |
} | |
String::Literal String::operator[](size_t j) | |
{ | |
// Переупорядочил ветви, чтобы сначала шёл нормальный ход работы. | |
// Кстати, переход на беззнаковый size_t заодни избавил нас от необходимости проверки | |
// индекса на неотрицательность, ибо это теперь железобетонное ограничение самого типа. | |
if(j < sizeOfArray) | |
{ | |
// Нам нужен указатель на указатель. Думаем, где его взять. | |
// | |
// Для этого рассмотрим схему размещения данных: | |
// | |
// | |
// char** str | |
// | +---+---+---+---+---+----- | |
// char* +---> | 0 | 1 | 2 | 3 | 4 | ... | |
// +---+---+---+---+---+----- | |
// | | |
// char, ... +---> H e l l o , W o r l d | |
// | |
// То есть для доступа к буферу достаточно копии указателя из второго уровня. | |
// Однако для модификации второго уровня нам нужен указатель на соответствующий | |
// элемент этого уровня: | |
// | |
// +--- (*) <- Вот этот указатель | |
// char** str v | |
// | +---+---+---+---+---+----- | |
// char* +---> | 0 | 1 | 2 | 3 | 4 | ... | |
// +---+---+---+---+---+----- | |
// | | |
// char, ... +---> H e l l o , W o r l d | |
// | |
// Соответственно, нам достаточно вызвать str[j], которая вернёт ссылку на | |
// элемент второго уровня, а затем преобразовать ссылку в указатель оператором | |
// "&". Это работает, потому что на уровне компилятора ссылка является синонимом | |
// какой-то переменной и не имеет собственного адреса. | |
return Literal(&str[j]); | |
} | |
else | |
{ | |
// Как-то странно безапеляционно прибивать программу вместо выброса исключения, | |
// которое вызывающая сторона могла бы обработать. Но это уже на усмотрение | |
// автора исходного кода | |
exit(1); | |
} | |
} | |
String::Literal::Literal(char** handledString) | |
: str_(handledString) | |
{ | |
} | |
void String::Literal::operator +(char *s) | |
{ | |
// Вот почему мы держали двойной указатель. Вообще, мы могли бы держать сразу | |
// ссылку на указатель, но ссылки принято использовать там, где они долго не живут. | |
// Поле класса в подобный сценарий не вписывается. | |
StrCat(*str_, s); | |
} | |
String::Literal::operator char*() const | |
{ | |
//Второй уровень указателей нам был нужен для внутренни манипуляциц. Пользователю | |
// класса он не нужен, поэтому разыменовываем. | |
return *str_; | |
} | |
String::Literal& String::Literal::operator=(const char* in) | |
{ | |
// Так как мы принимаем константные (то есть неизменяемые) строки, | |
// то для дальнейшей корректной работы operator+ входную строку | |
// надо скопировать в свой внутренний буфер | |
::StrCpy(*str_, in); | |
return *this; | |
} | |
static size_t StrLen(const char* _str) | |
{ | |
size_t size = 0; | |
// Мы работаем со строками, поэтому вместо нуля стоит использовать литерал нуль-символа | |
for (; _str[size] != '\0'; size++); | |
return size; | |
} | |
static void StrCpy(char* in_str, const char* src_str) | |
{ | |
for (size_t i = 0; i < StrLen(in_str); i++) | |
in_str[i] = src_str[i]; | |
} | |
static char* StrCat(char*& str1, const char* str2) | |
{ | |
const size_t sz = StrLen(str1) + StrLen(str2); | |
char* const ts = new char[sz + 1]; | |
for (size_t i = 0; i < StrLen(str1); i++) | |
ts[i] = str1[i]; | |
for (size_t ii = StrLen(str1), j = 0; ii <= sz; ii++, j++) | |
ts[ii] = str2[j]; | |
delete str1; | |
// Вы переопределяете значение _ копии_ под именем str1. Оригинальный | |
// указатель при этом продолжает указывать на уже освобождённый участок | |
// памяти. Исправил, сделав str1 ссылкой на указатель. | |
str1 = ts; | |
return str1; | |
} | |
int main() | |
{ | |
String a(3); | |
String b(6); | |
a[0] = "1"; | |
a[1] = "2"; | |
a[2] = "3"; | |
a[0] + a[1]; | |
std::cout << a[0] << std::endl; | |
system("pause"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment