Простой строковый класс с операторами


Приветствуется любой вклад! И могу я получить некоторые советы о том, как правильно удалить динамически выделенную память деструктор? Мой профессор сказал, что мы должны удалить каждый отдельный элемент массива, прежде чем делать удалить [] массив, и я не уверен, как я хотел бы сделать это здесь, потому что я не знаю длину массива.

Эти инструкции по назначению http://voyager.deanza.edu/~bentley/cis22b/ass8.html

#include <iostream>
#include <cstring>


using namespace std;


class String
{
private:
    char* data;
    static int numA;

public:
    String();
    String(const string&);
    String(const String&);
    ~String();
    static int a_count()
    {
        return numA;
    }
    void countA();
    void lowerNumA();
    bool operator < (const String& obj) const;
    bool operator > (const String& obj) const;
    bool operator == (const String& obj) const;
    void operator = (const String& obj);
    void operator += (const String& obj);
    char* operator + (const String& obj) const;
    int operator ! () const;
    char operator[] (int) const;
    char* operator * () const
    {
        return data;
    }
    friend ostream & operator << (ostream&, String&);

};

String::String()
    {
        data = new char[0];
        data[0] = '\0';
        countA();
    }


String::String(const string& thing)
    {
        data = new char[0];
        strcpy(data, thing.c_str());
        countA();
    }

String::String(const String &s2)
    {
        data = s2.data;
        countA();
    }

String::~String()
{
    delete [] data;
}

bool String::operator < (String const &obj) const
    {
        if(strcmp(data, obj.data) < 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

void String::countA()
    {
        for(int i = 0; data[i] != '\0'; i++)
        {
            if(data[i] == 'a' || data[i] == 'A')
                {
                    numA++;
                }
        }
    }


void String::lowerNumA()
    {
        for(int i = 0; data[i] != '\0'; i++)
        {
            if(data[i]=='a' || data[i]=='A')
            {
                numA--;
            }
        }
    }


bool String::operator > (String const &obj) const
    {
        if(strcmp(data, obj.data) > 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

bool String::operator == (String const &obj) const
    {

        if(strcmp(data, obj.data) == 0)
        {
            return true;
        }
        else
        {
            return false;
        }

    }

void String::operator = (String const &obj)
    {
        lowerNumA();
        data = obj.data;

        countA();
    }

void String::operator += (String const &obj)
    {
        lowerNumA();
        int element = 0;

        while(data[element] != '\0')
        {
            element++;
        }

        int element2 = 0;

        while(obj.data[element2] != '\0')
        {
            data[element] = obj.data[element2];
            element++;
            element2++;
        }

        data[element]='\0';
        countA();
    }

char* String::operator + (String const &obj) const
    {
        char* newChar = new char[100];
        int newCount = 0;
        int index = 0;

        while(data[index]!='\0')
        {
            newChar[newCount]= data[index];
            newCount++;
            index++;
        }

        index = 0;

        while(obj.data[index] != '\0')
        {
            newChar[newCount] = obj.data[index];
            newCount++;
            index++;
        }

        newChar[newCount] = '\0';

        cout << "";

        return newChar;

    }

int String::operator ! () const
    {
        int index = 0;
        int size = 0;

        while(data[index] != '\0')
        {
            size++;
            index++;
        }

        return size;
    }

char String::operator[] (int inIndex) const
    {
        int index = 0;
        while(data[index] != '\0')
        {
            index++;
        }

        if(inIndex >= 0 && inIndex <= index)
        {
            return data[inIndex];
        }
        else
        {
            cout<<"out of bound\n";
            return ' ';
        }
    }


int String::numA;

int main()
{
    // Constructors
    String A("apple");
    String B("banana");
    String C("cantaloupe");
    String D(B);
    String E;

    // static member function
    cout << "Number of a's = " << String::a_count() << endl << endl;

    // Overloaded insertion operator
    cout << "A = " << A << endl;
    cout << "B = " << B << endl;
    cout << "C = " << C << endl;
    cout << "D = " << D << endl;
    cout << "E = " << E << endl << endl;

    // Relational operators
    cout << boolalpha;
    cout << "A < B " << (A < B) << endl;
    cout << "B < A " << (B < A) << endl;
    cout << "A == B " << (A == B) << endl << endl;

    // Assignment operator
    A = B;
    cout << "A = " << A << endl;
    cout << "A == B " << (A == B) << endl << endl;

    // Size (bang) operator
    cout << "A size = " << !A << endl;
    cout << "E size = " << !E << endl << endl;

    // Unary * operator
    cout << "C text = " << *C << endl << endl;

    // Plus operator
    cout << "A + B = " << A + B << endl << endl;

    // Plus equal operator
    A += C;
    cout << "A = " << A << endl << endl;

    // Index operator
    cout << "A[3] = " << A[3] << endl << endl;

    // static member function
    cout << "Number of a's = " << String::a_count() << endl;
}

ostream & operator << (ostream& out, String& things)
{
    int index = 0;

    while(things.data[index] != '\0')
    {
        out << things.data[index];
        index++;
    }

    return out;
}


Комментарии
2 ответа

Хорошие вещи

В случае, если этот комментарий звучит грубо, я начну с пару вещей, которые мне действительно нравится этот код:


  • Вы уже позаботились соответствующие new[] с delete[] - это легко испортить

  • Ты заботишься с const (вы пропустите один, хотя - попробуйте изменить B для const String и вы увидите это).


Избежать using namespace std;

Импортируются все имена пространства имен-это вредная привычка, чтобы попасть, и может вызвать удивление, когда имена, как begin и size находятся в глобальном пространстве имен. Привыкайте использовать префикс пространства имен (std намеренно очень короткий), или импортировать просто имена, вам нужно в мельчайших разумных пределах.

Исключениями из этого правила являются пространства имен явным образом предназначена, чтобы быть импортированы оптом, такие как std::literals пространства имен.

Используйте инструменты, чтобы помочь

Пока эта программа будет правильно работать, запустить его под Valgrind показывает несколько утечек памяти и использование неинициализированных данных/нераспределенного:

 Invalid write of size 1
at 0x4C2FE80: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x1099A8: String::String(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (189465.cpp:52)
by 0x109E63: main (189465.cpp:229)
Address 0x5b34c80 is 0 bytes after a block of size 0 alloc'd
at 0x4C2D91F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x10997D: String::String(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (189465.cpp:51)
by 0x109E63: main (189465.cpp:229)

Invalid write of size 1
at 0x4C2FE8D: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x1099A8: String::String(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (189465.cpp:52)
by 0x109E63: main (189465.cpp:229)
Address 0x5b34c85 is 5 bytes after a block of size 0 alloc'd
at 0x4C2D91F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x10997D: String::String(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (189465.cpp:51)
by 0x109E63: main (189465.cpp:229)

...

 Invalid free() / delete / delete[] / realloc()
at 0x4C2E7BB: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x109A0C: String::~String() (189465.cpp:64)
by 0x10A59C: main (189465.cpp:229)
Address 0x5b34cc0 is 0 bytes after a block of size 0 free'd
at 0x4C2E7BB: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x109A0C: String::~String() (189465.cpp:64)
by 0x10A56F: main (189465.cpp:232)
Block was alloc'd at
at 0x4C2D91F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x10997D: String::String(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (189465.cpp:51)
by 0x109EB7: main (189465.cpp:230)

...

 HEAP SUMMARY:
in use at exit: 100 bytes in 2 blocks
total heap usage: 7 allocs, 7 frees, 73,828 bytes allocated

0 bytes in 1 blocks are definitely lost in loss record 1 of 2
at 0x4C2D91F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x10997D: String::String(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (189465.cpp:51)
by 0x109E63: main (189465.cpp:229)

100 bytes in 1 blocks are definitely lost in loss record 2 of 2
at 0x4C2D91F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x109CA3: String::operator+(String const&) const (189465.cpp:162)
by 0x10A40D: main (189465.cpp:264)

LEAK SUMMARY:
definitely lost: 100 bytes in 2 blocks
indirectly lost: 0 bytes in 0 blocks
possibly lost: 0 bytes in 0 blocks
still reachable: 0 bytes in 0 blocks
suppressed: 0 bytes in 0 blocks

For counts of detected and suppressed errors, rerun with: -v
ERROR SUMMARY: 444 errors from 78 contexts (suppressed: 0 from 0)

Документ ограничение дизайн

Уступка требования, кажется, требуют от нас провести только указатель на начало строки. По сравнению с std::string, который также был членом, чтобы держать длину строки, это означает, что наши объекты


  • будет меньше

  • не может содержать строку со встроенными нул

  • будет иметь представление о(n) а не O(1) для любого использования длины строки

Кроме того, мы не имеем std::string понятие потенциала , которая отличается от длины строки, так что каждая длина-операция изменения нужно обязательно требовать выделения (ну, может сократиться без перераспределения, но мы не знаем, как расточительно, что может быть).

Если вы хотите улучшить ваше понимание, я рекомендую вам экспериментировать дальше, чем требует задание - добавить length член и увидеть, что необходимо изменить, чтобы позволить встроенные нул. Вам понадобится std::memcpy() на месте std::strcpy() и std::strcat()так, например.

Кроме того, добавьте предупреждение комментарий к нетрадиционным operator! это требует задание - используя ! в значении "размер" является чуждым для C и C++ программистов, кто ожидал, что это будет логическое значение (true, если строка пуста или иначе "false" в некотором роде).

Достаточно выделить память для нулевой байт

Это одна из проблем, обнаруженных в отчет, в конструкторе:

    data = new char[0];
data[0] = '\0';

Если мы хотим написать data[0]нам нужно выделить как минимум 1 char (помните, количество в new[] выражение это , как многие объекты, выделять - индекс последнего будет на один меньше, чем количество):

    data = new char[1];
data[0] = '\0';

Я бы сказал, что лучше использовать инициализатор-члена для установки data (и ССЗ может предупредить, если вы забыли - это полезно):

String::String()
: data(new char[1])
{
data[0] = '\0';
countA();
}

Выделить достаточно памяти для содержимого

В std::string конструктора, нужно выделить достаточное количество символов для строки, мы копирования, включая завершающий нулевой символ:

String::String(const std::string& thing)
: data(new char[1+std::strlen(thing.c_str())])
{
std::strcpy(data, thing.c_str());
countA();
}

Я не использовала thing.size() потому что thing может иметь встроенный Нулс, и наша реализация будет рассматривать это как конец строки.

Используйте соответствующий тип для размеров

Строка может быть больше, чем диапазон int. Стандарт обеспечивает std::size_t которая будет достаточно большой для всего, что может поместиться в вашей программе адресного пространства, поэтому мы должны использовать это для длины, индексации и за наш счет a персонажей.

Использование ожидаемой доходности типов для операторов

Реляционные операторы возвращают bool (это уже правильно). Операторы присваивания должны возвращать ссылку на назначения объекта, и бинарные операторы должны возвращать экземпляр распространенный тип аргументов. Это означает, что:

String& operator = (const String& obj);
String& operator += (const String& obj);
String operator + (const String& obj) const;

Ответственность за выделение ресурсов

Этот конструктор копирования имеет серьезные проблемы:

String::String(const String &s2)
{
data = s2.data;
countA();
}

Предположим, у меня есть

{
String a = "foo";
String b = a;
}

В конце блока, b разрушения, которые delete[] b.data. Тогда a разрушения, которые delete[] a.data. Но a.data такой же, как и b.dataтак у нас двойное удаление!

Что нам нужно сделать, это выделить новую память для b.data и скопировать содержимое a в него.

Построить из строковых литералов

Тестовый код содержит строковые литералы ("...") - нет необходимости конвертировать их в std::string для нашего конструктора просто извлекать массив символов. Вместо этого можно построить непосредственно из этих:

String(char const*);

Мы даже можем принять этот закон как конструктор по умолчанию, путем предоставления аргументов по умолчанию:

String(const char * = "");

Не обеспечивают записи вид из нашего буфера

Задание требует от нас, чтобы обеспечить operator*но мы-щедры с тем, что мы возвращаемся. Мы можем совместно использовать буфер только для чтения форме, просто изменив тип возвращаемого значения:

const char* operator * () const
{
return data;
}

Теперь вызывающий код получает указатель на постоянные персонажи - которые не могут быть написаны без приведения.

Рефакторинг сложение и вычитание графов

Единственная разница между countA() и lowerNumA стоит ли нам добавить или вычесть из нашей переменной. Мы можем сделать одну функцию для подсчета (а она может быть как частным, так как существующие методы должны были):

std::size_t String::count_a() const
{
// know your algorithms - if we had an end iterator, we could
// simply return std::count(begin, end, 'a')
std::size_t count = 0;
for (auto p = data; *p; ++p)
count += (*p == 'a'); // no-one asked for 'A'
return count;
}

Тогда мы можем заменить вызовы countA() и lowerNumA с numA += count_a(); и numA -= count_a(); соответственно.

Не забывайте, что оператор присваивания

Если нам нужно написать конструктор копирования, обычно это значит, что мы должны писать что-то подобное в operator=() - за исключением того, что сначала мы должны освободить наших существующего содержания (можно скопировать из деструктора).


(Упражнение - это работа для самостоятельного назначения? Если нет, можете ли вы это исправить?)

Сократить дублирование в + и +=

Назначение и номера-присвоение двоичных операторов приходят парами. Вместо того, очень похожий код в двух местах, мы можем реализовать с точки зрения других.

Вот первая попытка:

String& String::operator += (String const &obj)
{
auto prev = data;
data = new char[1+std::strlen(prev)+std::strlen(obj.data)];
std::strcpy(data, prev);
delete[] prev;
std::strcat(data, obj.data);
numA += obj.countA();

return *this;
}

// Not an efficient method, but simple and easy to get correct
// Consider implementing += as '*this = *this + other'
String String::operator + (String const &obj) const
{
String s = *this;
return s += obj;
}


(Упражнение - что произойдет, если мы сделаем a += a;? Как мы можем решить эту проблему?)

Мы можем сделать operator+ эффективнее, если мы предоставим конструктор, который принимает две строки, так что он только выделяет еще и реализовать += с точки зрения +:

String::String(String const &a, String const& b)
: data(new char[1+std::strlen(a.data)+std::strlen(b.data)])
{
std::strcpy(data, a.data);
std::strcat(data, b.data);
numA += countA();
}

String& String::operator += (String const &obj)
{
return *this = *this + obj;
}

String String::operator + (String const &obj) const
{
return String(*this, obj);
}

Полное реляционные операторы

Если у нас есть < и == операторы, остальные могут быть определены с точки зрения их:

// member functions
bool String::operator < (String const &obj) const
{
return std::strcmp(data, obj.data) < 0;
}

bool String::operator == (String const &obj) const
{
return std::strcmp(data, obj.data) == 0;
}

// non-members
bool operator > (String const& a, String const& b)
{
return b < a;
}

bool operator != (String const& a, String const& b)
{
return !(a == b);
}

bool operator >= (String const& a, String const& b)
{
return !(a < b);
}

bool operator <= (String const& a, String const& b)
{
return !(b < a);
}

Кроме того, нечлены могут быть реализованы с

using namespace std::rel_ops;

Это является пространством имен, которое предназначено для импорта оптом, в отличие от namespace std.


Более продвинутые темы

Рассмотреть вопрос о потокобезопасности

Этот код выполняется в одном потоке, и это нормально. Если мы хотим использовать наши строки в разных потоках, то придется иметь дело с параллельным доступом к статическому numA переменной. Мы могли бы использовать мьютекс, но это дело обычное, так что мы можем получить все выгоды, просто изменив тип:

static std::atomic<std::size_t> numA;

Обеспечить переезд-конструктор и Move-назначение

Мы можем сделать код более эффективным, избегая выделения при копировании от временных объектов и другие модули ("истекает") объекты. Эти методы проще реализовать с точки зрения замены (рекомендую, но не показывайте, а swap() способ вместо простого std::swap() членов используется здесь):

String::String(const String&& s2)
: String()
{
std::swap(data, s2.data);
}

String& String::operator = (String&& obj)
{
std::swap(data, obj.data);
return *this;
}

Нам не нужно делать какие-либо выделения, если нас поменять местами, и мы также не должны регулировать количество, как мы просто меняя собственности буферов.


Дальнейшее чтение и упражнения

Обзор, что может быть стоит прочитать:

Дополнительные практики, в примерном порядке сложности:


  • Добавить член, чтобы отслеживать длину строки, как уже упоминалось выше в обзоре. Использовать такой длины, чтобы правильно копировать и объединять, когда строка имеет внедренные символы null (вы должны прекратить использование std::strlen() и std::strcpy()/std::strcat() - вы найдете std::memcpy() полезно).

  • Управление емкостью отдельно от длины.

  • Добавить итераторы, поэтому мы можем использовать стандартные алгоритмы, такие как std::count() (см. комментарий пример добавления в STL итератор поддержка пользовательской коллекции класса для каких-то подвохов.

  • Реализовать класс смотреть, что предоставляет подстроку существующую строку.


Измененный код

#include <atomic>
#include <iostream>
#include <cstring>

class String
{
static std::atomic<std::size_t> numA;

char* data; // no embedded NULs

std::size_t countA() const;
String(String const &a, String const& b); // for operator+

public:
String(const char * = ""); // argument must not be null
String(const String&);
String(String&&);
~String();

String& operator = (String);
bool operator < (const String&) const;
bool operator == (const String&) const;
String& operator += (const String&);
String operator + (const String&) const;
std::size_t operator ! () const; // unconventional (but required by exercise)
char operator[] (std::size_t) const;
const char* operator * () const { return data; }

friend std::ostream & operator << (std::ostream&, const String&);

static std::size_t a_count() { return numA; }
};

String::String(const char *s)
: data(new char[1+std::strlen(s)])
{
std::strcpy(data, s);
numA += countA();
}

String::String(const String &s2)
: data(new char[1+std::strlen(s2.data)])
{
std::strcpy(data, s2.data);
numA += countA();
}

String::String(String&& s2)
: String()
{
std::swap(data, s2.data);
}

String::String(String const &a, String const& b)
: data(new char[1+std::strlen(a.data)+std::strlen(b.data)])
{
std::strcpy(data, a.data);
std::strcat(data, b.data);
numA += countA();
}

String::~String()
{
numA -= countA();
delete[] data;
}

std::size_t String::countA() const
{
std::size_t count = 0;
for (auto p = data; *p; ++p)
count += (*p == 'a');
return count;
}

// Group similar operators together
bool String::operator < (String const &other) const
{
return std::strcmp(data, other.data) < 0;
}

bool String::operator == (String const &other) const
{
return std::strcmp(data, other.data) == 0;
}

// non-member relational operators
// alternative: using namespace std::rel_ops;
bool operator > (String const& a, String const& b)
{
return b < a;
}

bool operator != (String const& a, String const& b)
{
return !(a == b);
}

bool operator >= (String const& a, String const& b)
{
return !(a < b);
}

bool operator <= (String const& a, String const& b)
{
return !(b < a);
}

// equivalent to destructor plus copy constructor
String& String::operator = (String other)
{
std::swap(data, other.data);
return *this;
}

String& String::operator += (String const &other)
{
return *this = *this + other;
}

String String::operator + (String const &other) const
{
return String(*this, other);
}

// N.B. this is the "length" operator, not negation
std::size_t String::operator ! () const
{
return std::strlen(data);
}

char String::operator[] (std::size_t i) const
{
return data[i]; // caller is expected to check range
}

std::atomic<std::size_t> String::numA = 0;

3
ответ дан 13 марта 2018 в 09:03 Источник Поделиться


  1. Вы используете using namespace std;.
    Это плохая идея, читать "Почему “с помощью пространства имен STD;” и считается плохой практикой?".

  2. Приятно, что вы позволили строить из std::string. Тем не менее, есть много недостатков, которые, особенно, как вы делаете это:


    • Вы добавляете зависимость <string>. И вы, очевидно, забыли добавить, что заголовок.

    • Вы не поддерживаете различных char_traits и / или распределителем.

    • Строительство из строки-литерала зависит от объезда через этот конструктор, который не только весьма неэффективен, но и явным образом не разрешено в инструкции.

    К счастью, все недостатки легко исцеляются путем перемещения в C++17 std::string_view, который подспудно разобравшись с std::string и многих других источников.


  3. После предыдущего пункта также можно сделать копию-строительство более эффективным. Вместо того, чтобы следовать std::strlen() С std::strcpy()можно использовать более эффективно std::copy_n() или std::memcpy().

  4. Добавить шаг-ctor, и выполнять друг-функции swap(a, b) просто, чтобы сделать вещи проще для вас.

  5. Вам выделить необходимую память в конструктор-тело, а не в конструктор инициализации списка. Это означает, что dtor будет работать, пока data инициализирован, если выделение не удается. Исправить.

  6. Ваше задание-это катастрофа. Вам нужно сделать глубокую копию, т. е. копию содержала строку, вместо мелкой копией указателя. Не забудьте освободить старую строку, сделать его самостоятельное назначение просмотра, и помните, что выделения могут кинуть.
    На самом деле, переписать его с помощью копирования и своп-идиома.
    И помните, что она должна возвращать *this по ссылке.

  7. operator+ должны вернуть Stringне владея необработанного указателя. Кроме того, не предполагается, что char[100] хватает. Наконец, почему вы выводите пустую строку там, это довольно дорого нет?

  8. operator+= должны вернуть *this по ссылке, как и все присваивания-операторы. Ваша реализация просто предполагает, вопреки всякой очевидности, что в массиве, на который указывает data является достаточно большой, чтобы вместить обьединении вторая строка в конце. Если вторая строка пуста, ты пишешь из-за границы, который является неопределенное поведение (УБ). Исправить путем повторного использования operator +.

  9. countA() и lowerNumA() не должны быть открытыми, а их реализация-детали и не должны быть вызваны из вне, когда-либо. Кроме того, вы можете легко объединить их.

  10. Я предлагаю сделать статические данные-члены в std::size_t. Пока что не совсем гарантированно быть достаточно большим, если вы создаете миллионы строк, чтобы держать графа на всех реализациях, он избегает УБ на переполнение и те редкие, вероятно, также не в состоянии обеспечить std::uintptr_t.

  11. Вы можете только создать / уничтожить / append для экземпляров String в одном потоке, как статические члены не обращались в потокобезопасным способом. К счастью, вы можете легко исправить это с помощью std::atomicбез необходимости вводить замки.

  12. Спасибо за то, что пытался обеспечить полный набор операторов сравнения. Еще, ты забыл <=, >= и !=.

  13. Пока есть причина, чтобы вернуть внутренний буфер с одинарным * (хотя спец позволит возвращаясь *thisможет, по ссылке), вы действительно должны защитить внутренний буфер от модификации.

  14. Вы можете возвращать логическое выражение напрямую. Ветвление на нем и возвращение соответствующие литералы в филиалах многословен и ошибок. К счастью, компилятор не будет толку, так что она не неэффективна.

  15. Вы знаете std::strlen()так почему бы вам не использовать его в operator !?

  16. Если вы настаиваете на проверке границ в operator[]бросать исключение, если вы обнаружили из-за границы индекса. Не просто произвольно написать какой-то поток и возвращать что-то воображаемое, так как это делает повторное использование и отладка гораздо труднее.

  17. Помещая каждый символ по отдельности в поток невероятно неэффективно. Воспользоваться тем, что у вас есть 0-терминированная строка и operator<< перегружен для этого.

  18. Рассмотрим следующие почти всегда auto где вы можете. Это удаляет ненужные нагромождения и повторения, повышает эффективность и уменьшает головные боли.

Реализация, в которой все рядные:

#include <string_view>
#include <cstring>
#include <ostream>
#include <algorithm>
#include <utility>
#include <atomic>

class String {
char* data;
auto countA() const noexcept {
std::size_t n = 0;
for (auto p = data; *p; ++p)
n += *p == 'A' || *p == 'a';
return n;
}
inline static std::atomic<std::size_t> numA;
String(std::string_view a, std::string_view b)
: data(new char[a.size() + b.size() + 1]) {
std::copy_n(a.data(), a.size(), data);
std::copy_n(b.data(), b.size(), data + a.size());
data[a.size() + b.size()] = 0;
numA += countA();
}
public:
String() : data(new char[1]) { *data = 0; }
String(std::string_view o) : data(new char[o.size() + 1]) {
std::copy_n(o.data(), o.size(), data);
data[o.size()] = 0;
numA += countA();
}
String(String&& o) noexcept : String() { swap(o, *this); }
~String() { numA -= countA(); delete [] data; }
String& operator=(String&& o) { swap(*this, o); return *this; }
String& operator=(std::string_view o) { swap(*this, String(o)); return *this; }

operator std::string_view() const noexcept { return o.data; }
friend void swap(String& a, String& b) noexcept { std::swap(a.data, b.data); }
static std::size_t a_count() { return numA; }

bool operator<(const String& o) const noexcept { return strcmp(data, o.data) < 0; }
bool operator>(const String& o) const noexcept { return strcmp(data, o.data) > 0; }
bool operator<=(const String& o) const noexcept { return strcmp(data, o.data) <= 0; }
bool operator>=(const String& o) const noexcept { return strcmp(data, o.data) >= 0; }
bool operator==(const String& o) const noexcept { return strcmp(data, o.data) == 0; }
bool operator!=(const String& o) const noexcept { return strcmp(data, o.data) != 0; }

char operator[](int i) const noexcept { return data[i]; }
auto operator!() const noexcept { return std::strlen(data); }
const char* operator*() const noexcept { return data; }
friend auto& operator<<(std::ostream& o, const String& s) { return o << s.data; }
auto& operator+=(const String& o) { return *this = *this + o; }
auto operator+(const String& o) const { return String(*this, o); }
};

2
ответ дан 13 марта 2018 в 11:03 Источник Поделиться