Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекцій по С.doc
Скачиваний:
0
Добавлен:
04.01.2020
Размер:
7.72 Mб
Скачать

5.8 Клас string: конструктори і присвоєння рядків, операції, відкладене копіювання

Клас string: конструктори і присвоєння рядків, операції, відкладене копіювання

Найпоширенішим типом, що вживається практично у кожному проекті, є тип string

. Його можна запрограмувати самому, але вживання власного типу string створить проблеми сумісності, утруднить вживання програми в інших умовах. Тому вживають стандартний клас string

, що є компонентом стандартної бібліотеки С++. Але клас string сам по собі цікавий, щоб окремо вивчити його можливу реалізацію.

Почнемо з вимог до класу string . Він повинен забезпечувати:

  • Створення рядка символів.

  • Ініціалізацію рядка символів символьним масивом або іншим рядком.

  • Копіювання одного рядка до іншого (присвоєння).

  • Читання та запис окремих символів рядка.

  • Порівняння двох рядків на рівність.

  • Конкатенацію двох рядків.

  • Обрахунок довжини рядка.

  • Перевірку рядка на пустоту.

  • Знищення рядка символів.

Приклади використання

string st1; // створення порожнього рядка

//створення і ініціалізація

string st2( "Приклад ініціалізації" );

string st3 = st2; //теж ініціалізація

string st4 (st2); //ще одна ініціалізація

st1 = st2; //копіювання присвоєнням

cout << st1[1]; //читання символу

st1[1] = ’п’; //заміна символу

if (st3 == st2) //порівняння рядків на рівність

cout << “рівні”;

st1 = st2 + st3; //конкатенація

st2 = st3 + ”рядка”; //теж конкатенація

st2 += st3; //конкатенація з дописуванням в кінець рядка

cout << st.size(); //обчислення довжини рядка

if ( st1.empty()) //перевірка на пустоту

cout << ”рядок порожній”;

~string st1; //знищення рядка

Перетворення char* до string:

string s1;

const char *pc = "a character array";

s1 = pc; // правильно

Навпаки не працює:

char *str = s1; // помилка компіляції

//Перетворення виконує класна функція c_str ():

char *str = sl.c_str(); // майже вірно

const char *str = sl.c_str(); // зовсім вірно

Ось фрагмент специфікації класу string . Це дещо спрощений у запису текст заголовного файлу із стандартної бібліотеки. Особливих пояснень цей текст не потребує, оскільки призначення класу звичайне. Мінімальні пояснення до тексту дані в коментарях.

class string

{

public:

typedef unsigned long size_type;

typedef char* iterator;

// Максимально можлива довжину тексту визначимо як

// const string::size_type string::npos = -1;

// оскільки максимально можливе число — це

// npos = 4 294 967 295 (2**32-1)

static const size_type npos;

// Набір конструкторів і деструктор

string();

string(const string& _X);

string(const char* _S);

// Буде використано перших _N символів з рядка _S

string(const char* _S, size_type _N);

~string();

// Оператор індексування

string& operator[](size_type _P0);

// Варіанти присвоєння

string& operator=(const string& _X);

string& operator=(const char* _S);

string& operator=(const char _C);

// Присвоєння з конкатенацією

string& operator+=(const string& _X);

string& operator+=(const char* _S);

// Конкатенація

string operator+(const string& _X) const;

// Порівняння рядків

bool operator==(const string& _X) const;

bool operator!=(const string& _X) const;

bool operator>(const string& _X) const;

bool operator>=(const string& _X) const;

bool operator< (const string& _X) const;

bool operator<=(const string& _X) const;

// Ввід-вивід

friend operator>>(istream &is, string& _X);

friend operator>>(ostream &os, const string& _X)

// Перетворення string в const char*

const char* c_str() const;

// Властивості рядка (розмір, пустота)

size_type length() const;

size_type size() const;

size_type capacity() const;

bool empty() const;

// Очищення рядка

void clear();

// Перестановка

void swap(string& _X);

friend void swap(string& _X, string& _Y);

// Пошук підрядка

size_type find

(const string& _X, size_type _P = 0) const;

// Взяття підрядка

string substr

(size_type _P = 0, size_type _M = npos) const;

// Ітератори

iterator begin();

iterator end();

// Дописування в кінець

string& append(const string& _X);

string& append(const char* _S);

string& append(const char* _S, size_type _N);

// Функції присвоєння

string& assign(const string& _X);

string& assign

(const string& _X, size_type _P, size_type _M);

string& assign(const char* _S);

string& assign(const char* _S, size_type _N);

string& assign(const char _C);

// Лексикографічне порівняння частин рядків

bool compare(const string& _X) const;

bool compare(size_type _P, size_type _M,

const string& _X) const;

// Вставка підрядка

string& insert(size_type _P0, const string& _X);

string& insert(size_type _P0,

const string& _X, size_type _P, size_type _M);

string& insert(size_type _P0, const char* _S);

string& insert(size_type _P0,

const char* _S, size_type _M);

// Стирання підрядка

string& erase(size_type _P0 = 0,

size_type _M = npos);

// Заміна підрядка

string& replace(size_type _P0,

size_type _N0, const string& _X);

string& replace(size_type _P0,

size_type _N0, const char* _S);

string& replace(iterator _F,

iterator _L, const string& _X);

private:

char* _allocator;

size_type _len;

size_type _capacity;

};

Дуже корисною вправою була б спроба самостійної власної реалізації операцій і функцій над рядками. А ми розглянемо ще одну специфікацію рядків, а саме рядків з відкладеним копіюванням DeferredString

, запропоновану Б.Страуструпом в Спеціальному виданні С++.

class DeferredString

{

private:

// Спільне зберігання однакових рядків різних об’єктів

struct Srep; //representation

Srep* rep;

public:

// Розрізнення індексування при читанні і запису

class Cref; //reference to char

// Генерування виключень при виході індексів за межі

class Range(); //errors

DeferredString();

DeferredString(const DeferredString&);

DeferredString(const char*);

~DeferredString();

DeferredString& operator=( const DeferredString&);

DeferredString& operator=( const char* );

// Перевірка виходу індексу за межі рядка

void check(int i) const;

char read(int i) const;

void write (int i, char c);

Cref operator[](int i);

char operator[](int i) const;

int size()const;

};

Нижче наведено реалізацію цього класу. Порожній рядок, як і будь-який новий, завжди викликає конструктор зображення:

DeferredString::DeferredString()

{

rep = new Srep(0,"");

}

DeferredString::DeferredString(const char* s)

{

rep=new Srep(strlen(s),s);

}

Копіювальний конструктор створює ще одне посилання на існуюче зображення

DeferredString::DeferredString(const DeferredString& x)

{

x.rep->n++;

rep=x.rep; //shared representation

}

Деструктор знищує об'єкт лише у тому випадку, коли на нього немає більше посилань. Інакше кількість посилань зменшується на одиницю

DeferredString::DeferredString(const DeferredString& x)

{

x.rep->n++;

rep=x.rep; //shared representation

}

Як завжди, присвоєння відрізняється від коструктора необхідністю розпорядитися старим значенням лівої частини

DeferredString& DeferredString::

operator=(const DeferredString &x)

{

x.rep->n++;

// якщо присвоєння виду st=st

// то одночасно із зменшенням rep->n

// зменшиться також x.rep->n

// Старе значення витирається лише тоді,

// коли воно більш ніким не використовується

if (--rep->n==0) delete rep;

rep=x.rep;

return *this;

}

DeferredString& DeferredString::operator=(const char* s)

{

// Перевіряємо, чи хтось користується старим значенням

if (rep->n == 1)

// Якщо ні, виконуємо присвоєння на місці

rep->assign(strlen(s),s);

else

{

// Зменшуємо лічильник посилань

rep->n--;

// Будуємо нове зображення

rep=new Srep(strlen(s),s);

}

return *this;

}

Реалізації перевірки виходу індексу за межі і читання символу за його номером очевидні

void DeferredString::check(int i) const

{

if (i<0||rep-<sz<=i)

{

throw Range();

}

}

char DeferredString::read(int i) const

{

return rep->s[i];

}

Запис нового символу приводить до створення нового зображення. Можна було б передбачити вдосконалення реалізації для випадку, коли новий символ співпадає зі старим

void DeferredString::write (int i, char c)

{

rep=rep->getOwnCopy();

rep->s[i]=c;

}

Оператор індексування тепер перевірить спочатку правильність індексу, а потім створить об'єкт Cref

(про нього нижче)

DeferredString::Cref DeferredString::operator[](int i)

{

check(i);

return Cref(*this,i);

}

char DeferredString::operator[](int i) const

{

check(i);

return rep->s[i];

}

int DeferredString::size()const

{

return rep->sz;

}

Структура спільного зображення має досить зрозумілу специфікацію

struct DeferredString::Srep

{

char* s; //pointer to characters

int sz; //number of elements

int n; //number of references

Srep (int nsz, const char* p);

~Srep();

Srep* getOwnCopy();

void assign (int nsz, const char* p);

private:

Srep (const Srep&);

Srep& operator= (const Srep&);

};

Зверніть увагу на закриту частину структури.

До неї внесені оператори, замовчуваними версіями яких можна було

б ненароком зіпсувати всю систему відкладеного копіювання.

Конструктор створює нове зображення

Зверніть увагу на закриту частину структури. До неї внесені оператори, замовчуваними версіями яких можна було б ненароком зіпсувати всю систему відкладеного копіювання.

Конструктор створює нове зображення

деструктор його знищує.

DeferredString::Srep::~Srep()

{

delete [] s;

}

Тут відбувається явне копіювання рядка із створенням нового зображення

DeferredString::Srep* DeferredString::Srep::getOwnCopy()

{

if (n==1) return this;

n--;

return new Srep(sz,s);

}

Найнижчий рівень реалізації присвоєння

void DeferredString::Srep::assign(int nsz, const char* p)

{

if (sz!=nsz)

{

delete [] s;

sz=nsz;

s = new char[sz+1];

}

strcpy(s,p);

}

Ось клас для розпізнавання читання і запису

class DeferredString::Cref

{

friend class DeferredString;

DeferredString & s;

int i;

Cref(DeferredString& ss, int ii): s(ss), i(ii){}

public:

operator char() const {return s.read(i);}

void operator=(char c) {s.write(i,c);}

};

Корисно провести детальне спостереження за поведінкою об'єктів з відкладеним копіюванням.