
- •1 Парадигми та мови програмування
- •1.1 Процедурне програмування
- •1.2 Об'єктне (модульне) програмування
- •1.3 Об'єктно-орієнтовне програмування
- •2 Програмне середовище
- •3 Базові поняття програмування
- •4 Процедурно-орієнтоване програмування
- •4.1 Функції
- •4.2 Вбудовані (inline) функції
- •4.3 Передача параметрів
- •4.4 Обчислення значення функцій
- •4.4. Обчислення значення функції — вихід із функції; особливості повернення та використання іменованого значення, іменованої константи
- •4.5 Рекурсія
- •4.6 Довизначення (overloading) функцій
- •4.6. Довизначення (overloading) функцій
- •4.7 Узагальнені функції (function template)
- •4.8 Непряме використання функцій: указники на функції, їх тип, ініціалізація і присвоєння
- •4.9 Видимість
- •4.10 Тривалість життя об’єктів
- •5 Об'єктне програмування
- •5.1 Класи і об'єкти, члени класів
- •5.2 Інтерфейс класу, реалізація класу; визначення і оголошення класу
- •5.3 Створення і ініціалізація об'єктів, довизначення конструкторів, замовчуваний конструктор, копіювання, поверхневе і глибоке копіювання, ініціалізація і присвоєння, копіювальний конструктор
- •5.4 Cтатичні члени класів і статичні класні функції
- •5.5 Константні об'єкти, константні функції, змінювані члени константних об'єктів (mutable)
- •5.6 Поточний об'єкт this, указники на члени класу і класні функції, порівняння з указниками на (позакласні) функції, указники на статичні члени класу
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.8 Клас string: конструктори і присвоєння рядків, операції, відкладене копіювання
- •5.9 Параметризовані класи (class template): визначення шаблону, конкретизація
- •6 Об'єктно-орієнтовне програмування
- •6.1 Ієрархія об’єктів і ієрархія класів
- •6.2 Відкрите, закрите і захищене успадкування, успадкування типу і успадкування реалізації; абстрактні класи
- •6.3 Створення похідних об'єктів
- •6.4 Статичне і динамічне зв’язування
- •6.5 Успадкування із спільного базового класу. Кратне (multiple) успадкування. Віртуальне успадкування, порядок виклику конструкторів
- •7 Екзаменаційні завдання
- •7 Екзаменаційні завдання
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);}
};
Корисно провести детальне спостереження за поведінкою об'єктів з відкладеним копіюванням.