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

5.9 Параметризовані класи (class template): визначення шаблону, конкретизація

Параметризовані класи (class template): визначення шаблону, конкретизація

Розглянемо специфікацію масиву цілих

class IntArray

{

public:

IntArray (int sz=defaultArraySize){ init(sz,0);}

IntArray (int* array, int sz){init(sz,array);}

IntArray (const IntArray&){init(a._size, a.ia);}

~IntArray() {delete []ia;}

bool operator== (const IntArray&) const;

bool operator!= (const IntArray&) const;

IntArray& operator= (const IntArray&);

int& operator[] (int index);

int size() const;

void sort();

int min() const;

int max() const;

int find (int value) const;

private:

int _size;

int *ia;

bool _isSorted;

static const int defaultArraySize;

void init(int sz, int* array);

}

В ньому забезпечено зберігання додаткової інформації про розмір масиву, при

індексуванні можна виконувати перевірку виходу індексу за межі масиву. Також

передбачено зручні методи присвоєння масивів, сортування, пошуку, обчислення

мінімуму та максимуму, тощо.

Тепер уявімо собі, нам потрібен масив дійсних, комплексних, чи ще якихось

інших чисел. Тепер доведеться все переписувати, замінюючи де потрібно, int

на double ? — Звичайно, ні. Подібно узагальненим функціям, визначимо параметризований

масив Array елементів типу elemType

template <class elemType>

class Array

{

public:

Array (int sz=defaultArraySize);

Array (elemType* array, int sz);

Array (const Array&);

~Array() {delete []ia;}

bool operator== (const Array&) const;

bool operator!= (const Array&) const;

Array& operator= (const Array&);

elemType& opetator[] (int index);

int size() const;

void sort();

elemType min() const;

elemType max() const;

elemType find (elemType value) const;

private:

int _size;

elemType *ia;

static const int defaultArraySize=100;

void init(int sz, elemType* array);

}

Так виглядатимуть конкретизації — оголошення масивів конкретних типів

Array ia(sz);

Array cha(sz);

Array da(sz);

Array ca(sz);

Ось ще два приклади параметризованих класів

template <class T>

сlass Stack

{

public:

Stack();

void clear();

bool empty();

const T& top();

int pop();

void push(const T& value);

int size();

}

template <class T>

class Queue<T>

{

public:

Queue();

Queue(const Queue&);

virtual ~Queue();

Queue& operator=(const Queue&);

int operator==(const Queue&) const;

int operator!=(const Queue&) const;

void clear();

void append(const T&);

const T& front() const;

void pop();

int length() const;

bool empty() const;

};

6 Об'єктно-орієнтовне програмування

Зміст (обєктно-орієнтоване програмування)

6.1 Ієрархія об’єктів і ієрархія класів

Ієрархія об’єктів і ієрархія класів: композиція об’єктів і успадкування. Базовий клас, похідний клас, уточнення привілеїв доступу до членів базового класу, захищена область класу, область видимості і оператор розв’язування області видимості

Розглянемо простий приклад ієрархії двох взаємно пов'язаних класів

class IntArray

{

public:

IntArray (int sz=defaultArraySize);

IntArray (int* array, int sz);

IntArray (const IntArray&);

~IntArray() {delete []ia;}

bool operator== (const IntArray&) const;

bool operator!= (const IntArray&) const;

IntArray& operator= (const IntArray&);

int& operator[] (int index);

// Константне індексування довелося додати

// для реалізації функції

// int BoundedStack::top() const

int& operator[] (int index) const;

int size() const;

void sort();

int min() const;

int max() const;

int find (int value) const;

private:

int _size;

int *ia;

static const int defaultArraySize;

void init(int sz, int* array);

};

class BoundedStack

{

private:

static const int bos; //bottom of stack

int _top;

IntArray stackArray;

public:

class BadStack{};

explicit BoundedStack(int size):

stackArray(size),_top(bos){};

bool empty() const

{return _top==bos;}

bool full() const

{return _top==stackArray.size()-1;}

int top() const

{return stackArray[_top];}

int pop()

{

if (empty())

throw BadStack();

_top--;

}

void push(int value)

{

if (full())

throw BadStack();

stackArray[++_top]=value;

}

//delegating:

int size() const

{return stackArray.size();}

};

const int BoundedStack::bos=-1;

const int IntArray::defaultArraySize=256;

Таку ієрархію називають агрегацією або композицією . Обмежений

стек BoundedStack містить в собі масив IntArray , призначений для зберігання

елементів стеку. Виконання операцій над стеком делегується відповідним операціям

над масивом.

Цікаво зрозуміти, в який спосіб краще приєднати масив до стеку: так як ми

це зробили, а саме включенням атрибутом

IntArray stackArray ; //(1)

так, що масив стає частиною стеку, чи приєднання указником

IntArray * stackArray ; //(2)

при якому стек знає свій масив.

Ситуація схожа на клас string з відкладеним копіюванням. У випадку (1) агрегований

об'єкт стає частиною агрегату. Тому копіювання або знищення агрегату приводить

до копіювання або, відповідно, знищення агрегованого. У випадку (2) вони живуть

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

Суть агрегації можна висловити такими словами: стек використовує свій масив,

але сам стек не є масивом.

Візьмемо інший приклад: прямокутник і квадрат. Тут співвідношення інше — кожен

квадрат є прямокутником. Таку ієрархію називають успадкуванням. Візьмемо базовий

клас

class Rectangle

{

private:

short _height;

short _width;

public:

Rectangle( short heigh, short width);

void displayArea();

};

Rectangle::Rectangle(short height, short width)

{

_height = height;

_width = width;

}

void Rectangle::displayArea()

{

cout << "Area is: " <<_height * _width <<'\n';

}

Тепер визначимо квадрат його підкласом

class Square : public Rectangle

{

public:

Square(short side );

};

Square::Square( short side ) : Rectangle( side, side ){}

Конструктор підкласу читається так: для того щоб створити квадрат, спочатку

треба створити прямокутник з рівними сторонами. Це, власне, поки що вся особливість

підкласу — обмеження на довжини сторін.

Клас прямокутника має, крім конструктора, єдиний відкритий метод — обчислення

площі. Цей метод стає доступним квадратам теж.

int main()

{

Rectangle* myRectangle;

Square* mySquare;

myRectangle = new Rectangle( 10, 15 );

myRectangle->displayArea();

mySquare = new Square( 10 );

// *mySquare має крім типу Square також тип Rectangle

// а тому одержить доступ до його відкритого методу

mySquare->displayArea();

return 0;

}

Трохи несподіваним стило те, що квадрат більше не знає довжини свої сторони,

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

права доступу підкласів необхідно визначити точніше. Для цього в батьківському

класі визначається ще один рівень доступу — захищена частина класу protected

. Доступ до неї одержують лише підоб'єкти даного супероб'єкту.

class IntArray

{

public:

IntArray (int sz=defaultArraySize);

IntArray (int* array, int sz);

IntArray (const IntArray&);

~IntArray() {delete []ia;}

bool operator== (const IntArray&) const;

bool operator!= (const IntArray&) const;

IntArray& operator= (const IntArray&);

int& operator[] (int index);

int& operator[] (int index) const;

int size() const;

void sort();

int min() const;

int max() const;

int find (int value) const;

protected:

int *ia;

private:

int _size;

static const int defaultArraySize;

void init(int sz, int* array);

};

class BoundedStack: public IntArray

{

private:

static const int bos; //bottom of stack

int _top;

// IntArray stackArray;

public:

class BadStack{};

explicit BoundedStack(int size):

IntArray(size),_top(bos){};

bool empty() const

{return _top==bos;}

bool full() const

{return _top==size()-1;}

int top() const

{return ia[_top];}

void pop()

{

if (empty())

throw BadStack();

_top--;

}

void push(int value)

{

if (full())

throw BadStack();

ia[++_top]=value;

}

};

Тепер стек вже не містить більше в собі масиву, стек сам стає масивом (ще

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

напряму використовується потрібна функція масиву. Але є й небажані наслідки.

В результаті успадкування у нас з'явився гібрид масиву і стеку. Дійсно всі

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

BoundedStack s (100);

можна звертатися до нього, як до масиву

cout<<s.size(); //OK

cout<<s.size(); //OK

s[55] = 10; //???

cout <<s[99];

Успадкування вимагає деталізації.