З предмету «Об’єктно орієнтоване програмування» 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

З предмету «Об’єктно орієнтоване програмування»



Курс лекцій

З предмету «Об’єктно орієнтоване програмування»

 

Частина 3 – «Об’єктно орієнтоване програмування в мові C ++»


 

 

Зміст

Тема 11. Успадковування в С++

Тема 12. Вказівники

Тема 13. Віртуальні функції


Тема 11. Успадковування в С++

 

Вступ

Базовий і похідний класи

Визначення похідного класу

Доступ до базового класу

Специфікатор доступу protected

Недоліки використання специфікатора protected

Конструктори похідного класу

Перезавантаження функцій

Складніший приклад успадковування

Ієрархія класів

Загальне і приватне успадковування

Комбінації доступу

Рівні успадковування

Множинне успадковування

Методи класів та множинне успадковування

Конструктори при множинному успадковуванні

Невизначеність при множинному успадковуванні

Включення: класи в класах

Роль успадковування при розробці програм

Підсумок

Питання по темі

 

Вступ

Найбільш значимою після класів можливістю ООП є успадковування. Як ми вже знаємо з вивчення Паскалю, це процес створення нових класів, які називаються дочірніми, спадкоємцями або похідними класами з уже існуючих – батьківських або базових. Похідний клас одержує всі можливості базового класу, але може також бути удосконаленим за рахунок додавання власних. базовий клас при цьому залишається незмінним.

В деяких мовах базовий клас називається надкласом, а похідний – підкласом.

Виграш від успадковування полягає в тому, що воно дозволяє використовувати існуючий код кілька раз. Маючи написаний і відлагоджений базовий клас, ми можемо більше його не модифікувати, при цьому механізм успадковування дозволить нам пристосувати його для роботи в різних ситуаціях.

Важливим результатом повторного використання коду є також спрощення поширення бібліотеки класів. Програміст може використовувати класи, створені кимсь іншим, без модифікації кодів, просто створюючи похідні класи, які підходять до конкретної ситуації.

 

Базовий і похідний класи

В програмах 9.4 і 10.1 ми працювали з класом Counter, який використовували в ролі лічильника. Він міг бути ініціалізованим нулем чи певним числом при використанні конструктора, збільшений методом operator++() і прочитаний за допомогою методу get_count().

Припустимо, що нам потрібний ще один додатковий метод для зменшення лічильника, при цьому ми не хочемо або не маємо змоги змінювати вихідний код класу. Використаємо успадковування для створення нового класу на базі Counter. Далі представлений лістінг програми 11.1, в якому з’явиться новий клас CountOn, що додає операцію зменшення до вихідного класу.

#include <iostream.h>

#include <conio.h>

#include <bios.h>

class Counter

{protected:

unsigned int count;

public:

Counter():count(0) //constructor

{ }

Counter (int c):count(c)

{ }

 

unsigned int get_count()

{return count;}

 

Counter operator++ ()

{return Counter(++count);

}

};

 

class CountOn:public Counter //похідний клас

{public:

Counter operator--()

{return Counter(--count);

}

};

////////////////////

int main()

{

clrscr();

CountOn c1;

cout <<"\nc1="<<c1.get_count();

++c1;++c1;++c1;

cout <<"\nc1="<<c1.get_count();

--c1;--c1;

cout <<"\nc1="<<c1.get_count();

cout <<endl;

bioskey(0);

return 0;

}

 

Програма 11.1

Програма починається з опису класу Count, який залишився майже незміненим. Операцію постфіксного збільшення ми заради простоти не розглядаємо.

 

Визначення похідного класу

Після опису класу Count в програмі визначений новий клас CountOn. Він включає в себе новий метод: operator—(), який зменшує лічильник. В той же час, новий клас успадковує всі можливості базисного класу: конструктор і методи.

В першому рядку опису класу CountOn вказується, що він є похідним від Counter

Доступ до базового класу

Важливим компонентом успадковування є знання того, коли для об’єктів похідного типу можуть бути використані методи базового класу. Розглянемо їх на прикладі програми 11.1.

Незмінність базового класу

Згадаємо, що при успадковуванні базисний клас залишається незмінним. В функції main() програми 11.1 ми визначили об’єкт типу Counter

Counter c2;

Такі об’єкти поводяться так, начебто клас CountOn не існує.

Зауважимо також, що успадковування не працює в зворотньому напрямку. Базовому класу і його об’єктам недоступні похідні класи. В нашому випадку це означає, що об’єкти типу Counter не можуть використовувати метод operator—() класу CountOn. Якщо ми хочемо мати можливість зменшувати лічильник, то об’єкт повинен бути класу CountOn.

 

CountOn():Counter()

{ }

CountOn(int c):Counter(с)

{ }

 

CountOn operator—()

{return CountOn(--count);

}

};

////////////////////

int main()

{

clrscr();

CountOn c1;

CountOn c2(100);

cout <<”\nc1=”<<c1.get_count();

cout <<”\nc2=”<<c2.get_count();

++c1;++c1;++c1;

cout <<”\nc1=”<<c1.get_count();

--c2;--c2;

cout <<”\nc1=”<<c1.get_count();

CountOn c3=--c2;

cout<<”\nc3=”<<c3.get_count();

cout <<endl;

bioskey(0);

return 0;

}

Програма 11.2

Програма використовує два нові конструктори класу CountOn. Це конструктор без аргументів

CountOn():Counter()

{ }

і конструктор з одним аргументом

CountOn(int c):Counter(с)

{ }

 

В першому конструкторі використана нова для нас можливість: ім’я функції після двокрапки. Вона використовується для виклику конструктора базового класу. Якщо ми запишемо в функції main()

 

CountOn c1;

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

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

Конструктор

CountOn(int c):Counter(с)

{ }

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

 

Перезавантаження функцій

Ми можемо визначити для похідного класу методи з такими ж іменами, як і в базового класу. В цьому випадку має місце перезавантаження функцій. Така можливість може знадобитися, якщо для об’єктів базового і похідного класу в нашій програмі використовуються однакові виклики.

Проілюструємо це на такому прикладі. Одним з видів структур даних є стек, тобто такий набір даних, в якому останній доданий елемент видобувається першим. В формі стеків реалізується архітектура мікропроцесорів, функції передають аргументи і зберігають повернуті адреси у вигляді стеків. Далі приведена програма 11.3 моделює простий стек

#include <iostream.h>

#include <conio.h>

#include <bios.h>

class Stack

{private:

enum{MAX=10};

int st[MAX];

int top;

public:

Stack()

{top=0;}

void push(int var)

{st[++top]=var;}

int pop()

{return st[top--];}

};

 

////////////////////

int main()

{

clrscr();

Stack s1;

s1.push(11);

s1.push(22);

cout<<”1: “<<s1.pop()<<endl;

cout<<”2: “<<s1.pop()<<endl;

s1.push(33);

s1.push(44);

s1.push(55);

s1.push(66);

cout<<”3: “<<s1.pop()<<endl;

cout<<”4: “<<s1.pop()<<endl;

cout<<”5: “<<s1.pop()<<endl;

cout<<”6: “<<s1.pop()<<endl;

 

bioskey(0);

return 0;

}

Програма 11.3

Ця програма має деякі недоліки: не відсікає спроб покласти в стек більше елементів, ніж він спроможний помістити або видобути зі стеку більше елементів, ніж там є.

Для виправлення цих дефектів створимо новий клас Stack2, похідний від Stack. Об’єкти похідного класу поводяться як елементи базового, але попереджають про спроби переповнити стек або видобути елемент з порожнього стеку.

Далі приведений лістінг програми 11.4

#include <iostream.h>

#include <conio.h>

#include <bios.h>

#include<process.h>

class Stack

{protected:

enum{MAX=10};

int st[MAX];

int top;

public:

Stack()

{top=0;}

void push(int var)

{st[++top]=var;}

int pop()

{return st[top--];}

};

//////////

class Stack2:public Stack

{public:

 

void push(int var)

{if (top>=MAX-1)

{cout<<"Стек повний";exit(1); }

Stack::push(var);

}

 

int pop()

{

if (top<=0)

{cout<<"\nСтек порожній\n"; bioskey(0);exit(1); };

return Stack::pop();

}

};

////////////////////

int main()

{

clrscr();

Stack2 s1;

s1.push(11);

s1.push(22);

s1.push(33);

cout<<endl<<s1.pop();

cout<<endl<<s1.pop();

cout<<endl<<s1.pop();

cout<<endl<<s1.pop();

cout<<endl;

bioskey(0);

return 0;

}

Програма 11.4

В цій програмі клас Stack такий сам, як і в попередній, за винятком того, що дані класу оголошені як protected.

 

Використання програми 11.5

В функції main() програми оголошені три інтервали. Інтервал alpha дістає значення від користувача, beta ініціалізується набором (11,6.25) і знаком + за замовчуванням, gamma – набором (100,5.5,neg), де neg означає знак -.

Клас DistSign є похідним класу Distance. До нього додане поле sign типу posneg. Поле sign призначене для зберігання знаку інтервалу. Тип posneg визначений в операторі enum і має два можливі значення – pos і neg.

 

Конструктори класу DistSign

Клас DistSign має два конструктори, таких самих, як і клас Distance. Перший не має аргументів, у другого два або три аргументи. Третій, необов’язковий, аргумент другого конструктора – це змінна sign, що приймає значення pos або neg. Ці конструктори дозволяють нам визначити об’єкти типу DistSign різними способами.

Обидва конструктори в класі DistSign викликає відповідні конструктори з класу Distance для встановлення значень футів і дюймів. Вони також встановлюють значення поля sign. Конструктор без аргументів завжди встановлює значення поля sign рівним pos. Другий конструктор встановлює значення поля sign як pos, якщо воно не визначене.

Аргументи ft і in, що передаються з функції main() другому конструктору класу DistSign, просто передаються конструктору класу Distance.

 

Методи класу Distance

Додавання поля sign в клас Distance має значення для обох його методів. Метод getdist() класу DistSign повинен послати запит користувачу про знак інтервалу. Метод showdist() повинен вивести знак інтервалу. Ці методи викликають відповідні методи з класу Distance

Distance::getdist();

Distance::showdist();

Таким чином, ми не дублюємо код, а викликаємо підходящі вже готові методи. Це повністю співпадає з загальною концепцією С++ і принципом повторного використання коду.

 

Ієрархія класів

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

В якості прикладів розглянемо базу даних службовців певної компанії. В ній існує три категорії службовців: менеджери, вчені і робітники. В базі даних зберігаються імена службовців всіх категорій та їх ідентифікаційні номери. В інформації про менеджерів міститься ще й назва їх посади і внески в клуб, в інформації про вчених – кількість публікацій.

Приклад нашої програми починається з опису базового класу employce. Цей клас містить прізвища працівників та їх номери. Він породжує три нових класи: manager, scientist і laborer.

Далі приведено лістінг програми 11.6, яка й реалізує всі ці класи

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 const int LEN=80;

 ////////////

 class employce

 {private:

 char name[LEN];

 unsigned long number;

 public:

 void getdata()

 {cout <<”\nВведіть ПІП: “;cin>>name;

 cout <<”\nВведіть номер: “;cin>>number;

 }

 void putdata() const

 {cout<<”\nПІП=”<<name;

 cout<<”\nНомер=”<<number;

 }

 };

 class manager:public employce

 {private:

 char title[LEN];

 double dues;

 public:

 void getdata()

 {employce::getdata();

 cout<<”\nПосада=”;cin>>title;

 cout<<”\nВнески=”;cin>>dues;

 }

 void putdata() const

 {employce::putdata();

 cout<<”\nПосада=”<<title;

 cout<<”\nВнески=”<<dues;

 }

 };

 //////////

 class scientist:public employce

 {private:

 int puts;

 public:

 void getdata()

 { employce::getdata();

 cout<<”\nПублікацій=”;cin>>puts;

 }

 void putdata() const

 { employce::putdata();

 cout<<»\nPublications=»<<puts;

 }

 };

 ////////////

 class laborer:public employce

 {

 };

 //////////

 int main()

 {clrscr();

 manager m1,m2;

 scientist s1;

 laborer l1;

//Vvid

cout<<endl;

cout<<”Менеджер 1\n”;

m1.getdata();

cout<<”Менеджер 2\n”;

m2.getdata();

cout<<”\nВчений 1”;

s1.getdata();

cout<<”\nРобітник 1”;

l1.getdata();

//Вивід

cout<<”\n\nM1”<<endl;

m1.putdata();

cout<<”\n\nM2”<<endl;

m2.putdata();

cout<<”\n\nS1”<<endl;

s1.putdata();

cout<<”\n\nl1”<<endl;

l1.putdata();

 

 

 bioskey(0);

 return 0;

 }

Програма 11.6

 

В функції main() цієї програми оголошені 4 об’єкти різних класів: два об’єкти класу manager, об’єкт класу scientist та об’єкт класу laborer. Вони викликають метод getdata() для одержання даних і метод putdata() для її виведення.

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

 

Абстрактний базовий клас

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

Клас laborer виконує ті ж функції, що й клас employce, оскільки не має жодних відмінностей від нього. Може здатися, що клас laborer – зайвий, але, створивши його, ми підкреслили, що всі класи мають одне спільне джерело – клас employce. Крім того, якщо ми захочемо в майбутньому модифікувати клас laborer, нам не треба буде міняти клас employce.

Класи, що використовуються тільки як базові для похідних, наприклад, як employce, інколи неточно називають абстрактними класами, маючи на увазі, що в цього класу немає об’єктів. Однак насправді термін абстрактний має дещо інше значення, ми вже з ним стикалися, знайомлячись з основами ООП в Delphi.

 

Конструктори і функції

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

manager m1,m2;

використовує конструктор, встановлений за замовчуванням для класу manager, що викликає конструктор класу employce. Методи getdata() і putdata() похідних класів викликають однойменні методи базових класів.

 

Комбінації доступу

Існує багато можливостей для доступу. Приведемо приклад, що показує, яка комбінація буде працювати, а яка – ні. Далі приведено лістінг програми 11.7

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 class A

 {private:

 int privdataA;

 protected:

 int protdataA;

 public:

 int pubdataA;

 };

 /////////////

 class B:public A

 {public:

 void funcs()

 {int a;

// a=privdataA; помилка

 a=protdataA;

 a=pubdataA;

 }

 };

///////////

class C:private A

{public:

void funct()

{int a;

 //a=privdataA; // помилка

 a=protdataA;

 a=pubdataA;

}

};

 ///////////

 int main()

 {clrscr();

 int a;

 B objB;

// a=objB.privdataA;//помилка

// a=objB.protdataA;//помилка

 a=objB.pubdataA;

 

C objC;

// a=objC.privdataA;//помилка

// a=objC.protdataA;//помилка

// a=objC.pubdataA; //помилка

 bioskey(0);

 return 0;

 }

Програма 11.7

В програмі описаний клас А, що має дані зі специфікаторами доступу private, public, protected. Клас В і С є похідними класами. Клас В є загальним спадкоємцем, клас С – приватним спадкоємцем класу А. Методи похідних класів мають доступ до даних базового класу, оголошений як public чи protected. Для об’єктів похідних класів відсутній доступ до членів базового класу, оголошених як protected чи private.

Що нового у відмінностях між загальними і приватними похідними класами? Об’єкти загального спадкоємця В мають доступ до членів класу А, оголошених як public чи protected, а об’єкти приватного спадкоємця класу С мають доступ лише до членів, оголошених як private.

Якщо не вказувати специфікатор доступу при створенні класу, то буде використаний специфікатор private.

 

Рівні успадковування

Похідні класи можуть виступати як базові класи для інших похідних класів. Розглянемо маленьку програму як приклад такого випадку:

class A

{ };

class B:public A

{ };

class c:public B

{ }

Тут клас В є похідним класу А, а клас С – похідним класу В. Процес може розвиватися далі.

Розглянемо конкретніший приклад. Припустімо, що ми хочемо ввести в організації (програма 11.7) посаду бригадира. Відповідний клас називатиметься foreman, базуватиметься на класі laborer і міститиме порівняно з цим класом додаткове поле «процент виконання норми виробітку».

Далі приведений лістінг програми 11.8.

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 const int LEN=80;

 ////////////

 class employce

 {private:

 char name[LEN];

 unsigned long number;

 public:

 void getdata()

 {cout <<”\nВведіть ПІП: “;cin>>name;

 cout <<”\nВведіть номер: “;cin>>number;

 }

 void putdata() const

 {cout<<”\nПІП=”<<name;

 cout<<”\nНомер=”<<number;

 }

 };

 class manager:public employce

 {private:

 char title[LEN];

 double dues;

 public:

 void getdata()

 {employce::getdata();

 cout<<”\nPosada=”;cin>>title;

 cout<<”\nVnesky=”;cin>>dues;

 }

 void putdata() const

 {employce::putdata();

 cout<<”\nПосада=”<<title;

 cout<<”\nВнески=”<<dues;

 }

 };

 //////////

 class scientist:public employce

 {private:

 int puts;

 public:

 void getdata()

 { employce::getdata();

 cout<<”\nПублікації=”;cin>>puts;

 }

 void putdata() const

 { employce::putdata();

 cout<<”\nПублікації=”<<puts;

 }

 };

 ////////////

 class laborer:public employce

 {

 };

 /////////

 class foreman:public laborer

 {private:

 double quotas;

 public:

 void getdata()

 {laborer::getdata();

 cout <<” Норма виробітку=”;cin>>quotas;

 }

void putdata() const

{laborer::putdata();

cout <<”\n Норма виробітку =”<<quotas;

}

 };

 //////////

 int main()

 {clrscr();

 laborer l1;

 foreman f1;

//Ввід

cout<<endl;

cout<<”\nРобітник 1”;

l1.getdata();

cout<<”\nБригадир 1”;

f1.getdata();

 

//Вивід

cout<<”\n\nl1”<<endl;

l1.putdata();

cout<<”\n\nf1”<<endl;

f1.putdata();

 

 bioskey(0);

 return 0;

 }

Програми 11.8

 

Множинне успадковування

Клас може бути похідним не тільки від одного базового класу, а й від багатьох. Цей випадок називається множинним успадковуванням. Синтаксис опису множинного успадковування схожий на випадок простого успадковування. Далі приведений приклад множинного успадковування:

class A

{ };

class B

{ };

clacc C:public A, public B

{ };

Базові класи класу С перелічені після двокрапки в рядку опису класу і розділені комами.

 

Конструктори без аргументів

В класі Type конструктор без аргументів виглядає так:

Type():dimensions(“N/A”),grade(“N/A”)

 { }

Цей конструктор присвоює значенням полів dimensions та grade значення рядка "N/A” (недоступно), тому при спробі вивести дані для об’єкту класу Lumber користувач знатиме, що поля порожні.

Ми вже використовували конструктор без аргументів в класі Distance

Distance():feet(0),inches(0.0)

 {}

Конструктор без аргументів класу Lumber викликає конструктори обох класів – Type і Distance

Lumber():Type(),Distance(),quantity(0),price(0.0)

 {}

Імена конструкторів базового класу вказані після двокрапки і розділені комами. При виклику конструктора класу Lumber починають роботу конструктори базових класів Type() і Distance(). При цьому ініціалізуються змінні quantity і price().

 

Включення: класи в класах

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

Якщо клас В є похідним від класу А, то ми кажемо, що клас В є частково класом А, оскільки клас В має всі характеристики класу А, а також свої власні. Тому часто успадковування називають взаємовідносинами узагальнення.

Включення називають взаємовідносинами типу «має»: бібліотека «має» книжки, або «частина цілого»: книжка є частиною бібліотеки.

В ООП включення з’являється, якщо один об’єкт є атрибутом іншого об’єкту. Розглянемо випадок, коли об’єкт класу А є атрибутом класу В:

class A

{ };

class B

{ A objA;

}

 

Використаємо в програмі 11.9 включення замість успадковування. Класи manager та scientist є похідними класів employce та student. В новій програмі 11.13 ці класи міститимуть копії класів employce та student як атрибути.

Далі приведений лістінг програми 11.13.

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 const int LEN=80;

 class student

 {private:

 char school[LEN];

 char degree[LEN];

 public:

 void getedu()

 {cout<<”School:”;

 cin>>school;

 cout<<”Degree:”;

 cin>>degree;

 }

 void putedu() const

 {cout<<”\nSchool=”<<school;

 cout<<”\nDegree=”<<degree<<endl;

 }

};

//////////////

class employce

 {private:

 char name[LEN];

 unsigned long number;

 public:

 void getdata()

 {cout <<”\nВведіть ПІП: “;cin>>name;

 cout <<”\nВведіть номер: “;cin>>number;

 }

 void putdata() const

 {cout<<”\nПІП=”<<name;

 cout<<”\nНомер=”<<number;

 }

 };

 //////////////

 class manager

 {private:

 char title[LEN];

 double dues;

 employce emp;

 student stu;

 public:

 void getdata()

 {emp.getdata();

 cout<<”\nПосада=”;cin>>title;

 cout<<”\nВнески=”;cin>>dues;

 stu.getedu();

 }

void putdata() const

 {emp.putdata();

 cout<<”\nПосада=”<<title;

 cout<<”\nВнески=”<<dues;

 stu.putedu();

 }

 };

///////////

class scientist

{private:

 int puts;

 employce emp;

 student stu;

 public:

 void getdata()

 { emp.getdata();

 cout<<”\nПублікації=”;cin>>puts;

 stu.getedu();

 }

 void putdata() const

 { emp.putdata();

 cout<<”\nПублікації=”<<puts;

 stu.putedu();

 }

 };

 ////////////

class laborer

 {private:

 employce emp;

 public:

 void getdata()

 {emp.getdata();}

 void putdata() const

 {emp.putdata();}

 };

 

////////////

 int main()

 {clrscr();

 manager m1;

 scientist s1,s2;

 laborer l1;

 cout<<endl;

 cout<<”\nManager 1:”;

 m1.getdata();

 

 cout<<”\nScientist 1:”;

 s1.getdata();

 

 cout<<”\nScientist 2:”;

 s2.getdata();

 

 cout<<”\nLaborer 1:”;

 l1.getdata();

 

 cout<<”Manager1\n”;

 m1.putdata();

 cout<<”Scientist1\n”;

 s1.putdata();

 cout<<”Scientist2\n”;

 s2.putdata();

 cout<<”Laborer1\n”;

 l1.putdata();

 bioskey(0);

 return 0;

 }

Програма 11.13

 

 

 

 

Класи manager та scientist містять члени об’єктного типу

employce emp;

student stu;

 

Підсумок

Клас, що називається похідним класом, може успадковувати можливості іншого класу, який називається базовим. При цьому в похідному класі можуть бути власні можливості, оскільки він є більш спеціалізованою версією базового класу. Успадковування надає ефективний спосіб розширення можливостей існуючих класів і розробки програм з використанням ієрархічних зв’язків між класами.

Важливим питанням є доступність членів базового класу для методів похідних класів та об’єктів похідних класів. До полів та методів базового класу, оголошених як protected, можуть мати доступ тільки методи похідного класу. Об’єкти зовнішніх класів, включаючи похідний клас, в цьому випадку доступу до базового класу не мають. Класи можуть бути загальними і приватними похідними базового класу. Об’єкти загального похідного класу мають доступ до членів базового класу, оголошених як public, а об’єкти приватного похідного класу доступу до них не мають.

Клас може бути похідним більше ніж одного базового класу. Цей випадок називається множинним успадковуванням. Також клас може міститися всередині іншого класу.

Включення – це відношення типу «має» або «є частиною», при цьому один клас містить об’єкти іншого класу.

Успадковування дозволяє використовувати програмний код повторно: в похідному класі можна розширити можливості базового класу без його модифікації навіть не маючи доступу до коду. Це призводить до появи гнучкості в процесі розробки програмного забезпечення і розширенню ролі програмних розробників.

 

Питання по темі

1. Призначення успадковування полягає в тому, щоб:

а) створювати більш загальні класи в більш спеціалізованих

б) передавати аргументи об’єктам класів

в) додавати можливості до існуючих класів без їх модифікації

г) покращити приховування даних та їх інкапсуляцію

 

2. Перевага використання успадковування полягає в:

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

б) полегшення створення класу

в) повторного використання коду

 

3. Вкажіть правильний перший рядок опису класу Bosworth, який є public-похідним класу Alphsonso

а) Alphonso::Bosworth(public)

б) class Bosworth: public Alphonso

в) public class Bosworth: Alphonso

 

4. Члени базового класу для доступу до них методів похідного класу повинні бути оголошені як

а) public або protected

б) public або private

в) private або protected

 

5. Нехай базовий клас містить метод basefunc(), а похідний клас не має методу з таким іменем. чи може об’єкт похідного класу мати доступ до методу basefunc()?

а) так, завжди

б) ні

в) так, якщо цей метод не є прихованим

 

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

а) так

б) ні

 

7. Припустимо, що базовий і похідний класи включають в себе методи з однаковими іменами. Який з методів буде викликаний об’єктом похідного класу, якщо не використовується операція дозволу імені?

а) метод похідного класу

б) метод базового класу

 

8. Оператор дозволу звичайно:

а) обмежує видимість змінних для певних методів

б) визначає, від якого базового класу створений похідний

в) розв’язує невизначеності

 

9. Припустімо, що існує клас Derv, похідний від базового класу Base. Напишіть оголошення конструктора похідного класу, який приймає один цілий аргумент і передає його в конструктор базового класу.

а) Derv(int i):Base(i) {};

б) Derv(Base(int i));

в)Base::Derv(int i);

 

10. Припустімо, що клас Derv є приватним похідним класу Base. Ми визначаємо об’єкт класу Derv, розміщений в функції main(). Через нього ми можемо дістати доступ до:

а) членів класу Derv, оголошених як public

б) членів класу Derv, оголошених як protected

в) членів класу Derv, оголошених як private

г) членів класу Base, оголошених як public

д) членів класу Base, оголошених як protected

е) членів класу Base, оголошених як private

 

11. Чи істинне твердження: клас D може бути похідним класу С, який є похідний від класу В, для якого є базовим клас А?

а) так

б) ні

 

12. Припустімо, що клас Derv є похідним від класу Base. Обидва класи містять метод func без аргументів. Як правильно викликати метод func() базового класу з методу класу Derv?

а) func();

б) Base::func();

в) Base:func();

 

13. Як правильно описати клас С, що є похідним класів А і В?

а) class C: public A, public B;

б) class C: public A B;

в) class C::public A, public B;

 

14. Чи істинне твердження: неможливо зробити об’єкт одного класу членом іншого класу?

а) так

б) ні

 

15. Включення – це

а) форма реалізації

б) форма узагальнення

в) відношення типу «має»

 

 


Тема 12. Вказівники

 

Вступ

Адреси і вказівники

Операція одержання адреси &

Змінні вказівники

Доступ до змінної за вказівником

Вказівник на void

Вказівники і масиви

Вказівники-константи і вказівники-змінні

Вказівники і функції

Передача простої змінної

Передача масиву

Вказівники на рядки

Вказівники на рядкові константи

Рядки як аргументи функцій

Копіювання рядків з використанням вказівників

Масив вказівників на рядки

Управління пам’яттю: операції new і delete

Операція new

Операція delete

Клас String з використанням операції new

Вказівники на об’єкти

Масив вказівників на об’єкти

Зв’язний список

Класи, що містять самі себе

Вказівники на вказівники

Самостійне вивчення

Підсумок

Питання по темі

 

Вступ

Використання вказівників є характерною особливістю С++, що створює чимало труднощів, особливо для початківців. Однак саме вони надають програмуванню на С++ гнучкості.

Найчастіші приклади використання вказівників:

· Доступ до елементів масиву

· Передача аргументів в функцію, від якої вимагається змінити ці аргументи

· Передача у функцію масивів і рядкових змінних

· Створення складних структур таких як зв’язний список.

Частина далі викладеного матеріалу знайома нам з курсу предмету «Алгоритмічні мови», але такі питання, як операції new і delete, доступ до функцій класу за допомогою вказівників, масиви вказівників на об’єкти та зв’язні списки в С++, є новими.

 

Адреси і вказівники

Ідея вказівників нам вже знайома з вивчення ООП у Паскалі. Кожен байт пам’яті має адресу. Завантажуючись в пам’ять, наша програма займає якусь кількість цих адрес. Це означає, що кожна змінна і кожна функція нашої програми починається з якоїсь конкретної адреси.

 

Операція одержання адреси &

Ми можемо одержати адресу змінної, використовуючи операцію одержання адреси &. Розглянемо невеличку програму 12.1, що показує, як це зробити.

#include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 int main()

 {clrscr();

 int var1=11;

 int var2=22;

 int var3=33;

cout<<&var1<<endl;

cout<<&var2<<endl;

cout<<&var3<<endl;

 

 bioskey(0);

 

 return 0;

 }

Програма 12.1

 

В цій простій програмі визначені три цілочисельні змінні, ініціалізовані значеннями 11, 22 і 33. Ми виводимо на екран адреси цих змінних.

Реальні адреси, зайняті змінними в програмі, залежать від багатьох факторів таких як комп’ютер, на якому запущена програма, розмір оперативної пам’яті, наявність іншої програми та ін. Тому в різних користувачів програма 12.1 даватиме різні результати і навіть у того самого користувача при різних запусках програми результати будуть різними.

 

Змінні вказівники

Адресний простір обмежений. Можливість дізнатися, де саме в пам’яті розміщені змінні, корисна, а бачити саме значення адреси потрібно досить рідко. Зате дуже потрібними були б змінні, що зберігають адреси. Такі змінні цілковито аналогічні до змінних, які зберігають числа чи знаки. Змінна, що містить в собі значення адреси, називається змінною-вказівником чи просто вказівником.

Змінна-вказівник не того ж типу, що змінна, адресу якої вона зберігає. В програмі 12.2 показаний синтаксис змінних-вказівників.

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 int main()

 {clrscr();

 int var1=11;

 int var2=22;

cout<<”&var1=”<<&var1<<endl;

cout<<”&var2=”<<&var2<<endl<<endl;

int* ptr;

ptr=&var1;

cout<<ptr<<endl;

ptr=&var2;

cout<<ptr<<endl;

 

 bioskey(0);

 

 return 0;

 }

Програма 12.2

В цій програмі визначені дві цілочисельні змінні var1 і var2, які ініціалізовані значеннями 11 і 22. Потім програма виводить на дисплей їх адреси.

Далі в програмі визначена змінна-вказівник в рядку

int* ptr;

Зірочка означає вказівник на. Отже, в цьому рядку визначена змінна ptr як вказівник на int, тобто ця змінна може містити в собі адресу змінної типу int. На відміну від Паскалю, в С++ не використовуються нетипізовані вказівники Pointer. Тобто, вказівник в С++ завжди вказує на якийсь конкретний тип.

Слід відмітити, що загальноприйнято визначати вказівник за допомогою зірочки, що пишеться перед іменем змінної, а не зразу після назви типу:

char *charptr;

Це не принципово для компілятора, але зірочка, розміщена відразу після назвою типу змінної, сигналізує, що це не просто тип, а вказівник на нього.

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

char* ptr1, *ptr2, *ptr3;

І в такому випадку можна використати стиль написання, при якому зірочка ставиться поруч з іменем:

char *ptr1, *ptr2, *ptr3;

Вказівнику обов’язково повинне бути присвоєне якесь значення, інакше він вказуватиме на випадкову адресу. Неініціалізований вказівник може призвести до краху системи, між тим, компілятор не виявляє помилок такого виду.

 

Вказівник на void

Відмітимо одну особливість вказівників. Адреса, яка поміщається у вказівник, повинна бути одного з ним типу. Ми не можемо присвоїти вказівнику на int адресі змінної типу float. Однак є один виняток: існує тип вказівника, який може вказувати на будь-який тип даних. Він називається вказівником на void і визначається так:

void* ptr; //вказівник на довільний тип даних

Такі вказівники призначені для використання в певних випадках, наприклад, при передачі вказівників у функції, що працюють незалежно від типу даних, на які вказує вказівник.

Розглянемо приклад використання вказівника на void. В цій програмі також показано, що, якщо ми не використовуємо вказівника на void, нам слід бути обережними, присвоюючи вказівнику адресу того ж типу, що й вказівник.

Далі приведений лістінг програми 12.5

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 int main()

 {clrscr();

 int intvar;

 float flovar;

 int* ptrint;

 float* ptrflo;

 void* ptrvoid;

 ptrint=&intvar;//так можна – однакові типи

 // ptrint=&flovar; //так не можна – різні типи

 ptrint=(int*)&flovar;

 // ptrflo=&intvar; //так не можна – різні типи

 ptrflo=(float*)&intvar; //так можна – примусове перетворення типів

 ptrflo=&flovar;

 ptrvoid=&intvar; //так можна - void

 ptrvoid=&flovar; //так можна - void

 

 bioskey(0);

 return 0;

 }

Програма 12.5

 

В цій програмі ми використали примусове перетворення типів «вказівник»

 

ptrflo=(float*)&intvar; //так можна – примусове перетворення типів

 

Вказівники і масиви

Вказівники і масиви дуже тісно пов’язані між собою. Фактично, ім’я масиву є вказівником на його перший член. Тому «переміщуватися вздовж» масиву можна з використанням вказівників, як показано в програмі 12.6.

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 int main()

 {clrscr();

int intarray[5]={31,54,77,52,93};

for(int j=0;j<5;j++)

cout<<*(intarray+j)<<endl;

 bioskey(0);

 return 0;

 }

Програма 12.6

 

 

Вказівники і функції

Передача аргументів функції може бути здійснена трьома шляхами: за значенням, за посиланням і за вказівником. Якщо функція призначена для зміни аргументу у викликуваній програмі, то ця змінна не може бути передана за значенням, оскільки функція дістає лише копію змінної. Однак в цій ситуації ми можемо передати змінну за посиланням і за вказівником.

 

Передача простої змінної

В програмі 12.8 показано, як змінна передається за посиланням, в програмі 12.9 (яка виконує такі самі дії, тобто перетворює дюйми на сантиметри) – за вказівником.

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 int main()

 {void centimize(double&);//прототип

 clrscr();

 double var=10.0;

 cout<<"var="<<var<<" дюйми"<<endl;

 

 centimize(var);

cout<<"var="<<var<<" см"<<endl;

 

bioskey(0);

 return 0;

 }

 ///////////

 void centimize(double& v)

 {v*=2.54;}

 

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

 

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 int main()

 {void centimize(double*);//прототип

 clrscr();

 double var=10.0;

 cout<<"var="<<var<<" дюйми"<<endl;

 

 centimize(&var);

cout<<"var="<<var<<" сантиметри”<<endl;

 

bioskey(0);

 return 0;

 }

 ///////////

 void centimize(double* ptrd)

 {*ptrd*=2.54;}

 

Програма 12.9



Поделиться:


Последнее изменение этой страницы: 2021-12-15; просмотров: 87; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.221.222.47 (0.609 с.)