Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Namespace CounterNameSpace 4 страница




● по-перше, у похідному класі можна використовувати оголошення using (цей спосіб рекомендується стандартом мови C++ для використання в новому коді програми). Але особливості використання директиви using відкладемо до розгляду теми простору імен[63];

● по-друге, можна налагодити доступ до успадкованого члена за допомогою оголошень доступу.

Зауважмо, що оголошення доступу все ще підтримуються стандартом мови C++, але останнім часом активізувалися заперечення проти його застосування, а це означає, що його не варто використовувати в новому коді програми. Позаяк воно все ще використовуються у мові програмування С++, то дещо приділимо увагу цій темі.

Оголошення доступу має такий формат:

ім'я_базового_класу::член;

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

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

class baseClass {

public:

int jzm; // public -доступ у класі baseClass

};

 

// Клас baseClass успадковується як private -клас.

class derived: private baseClass { // Оголошення похідного класу

public:

// Ось використання оголошення доступу:

baseClass::jzm; // Тепер член jzm знову став відкритим.

//...

};

Оскільки клас baseClass успадковується класом derived закритим способом, то його public -змінна jzm стає private -змінною класу derived. Проте внесення цього оголошення доступу

baseClass::jzm;

в класі derived під специфікатором public відновлює public -статус члена jzm.

Оголошення доступу можна використовувати для відновлення прав доступу public - і protected -членів. Проте для зміни (підвищення або пониження) статусу доступу його використовувати не можна. Наприклад, член, оголошений закритим у базовому класі, не можна зробити відкритим у похідному[64]. Використання оголошення доступу продемонстровано в такій програмі.

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

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class baseClass { // Оголошення базового класу

int izm; // private -член у класі baseClass

public:

int jzm, kzm;

void setIzm(int x) { izm = x;}

int getIzm() { return izm;}

};

 

// Клас baseClass успадковується як private-клас.

class derived: private baseClass {

public:

/* Наступні три настанови перевизначають private -успадкування класу

baseClass і відновлюють public -статус доступу для членів jzm,

setIzm() і getIzm(). */

 

baseClass::jzm; // Змінна jzm стає знову public -членом

// а змінна kzm залишається закритим членом.

baseClass::setIzm; // Функція setIzm() стає public -членом.

baseClass::getIzm; // Функція getIzm() стає public -членом.

 

// baseClass::izm; // Неправильно: не можна підвищувати рівень доступу

 

int a; // public -член

};

 

int main ()

{

derived D_ob; // Створення об'єкта класу

 

// D_ob.izm = 10; // Неправильно, оскільки член izm

// закритий у класі derived.

D_ob.jzm = 20; // Допустимо, оскільки член jzm став

// Відкритим у класі derived

// D_ob.kzm = 30; // Неправильно, оскільки член kzm

// Закритий у класі derived.

D_ob.a = 40; // Допустимо, оскільки член а відкрито у класі derived.

D_ob.setIzm(10);

cout << D_ob.getIzm() << " " << D_ob.jzm << " " << D_ob.a;

 

getch (); return 0;

}

Звернемо Вашу увагу на те, як у цій програмі використовуються оголошення доступу для відновлення статусу public у членів jzm, setIzm() і getIzm(). У коментарях відзначені й інші обмеження, пов'язані із статусом доступу.

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

15.7. Віртуальні базові класи

Під час успадкування декількох базових класів у С++-програму може бути внесений елемент невизначеності. Для розуміння сказаного розглянемо таку некоректну програму, яка містить помилку і не скомпілюється.

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

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class baseClass { // Оголошення базового класу

public:

int izm;

};

 

// Клас derivedA успадковує клас baseClass.

class derivedA: public baseClass {

public:

int jzm;

};

 

// Клас derivedB успадковує клас baseClass.

class derivedB: public baseClass {

public:

int kzm;

};

 

/* Клас derivedC успадковує обидва класи derivedA і derivedB.

Це означає, що у класі derivedC існує дві копії класу baseClass! */

class derivedC: public derivedA, public derivedB {

public:

int sum;

};

 

int main ()

{

derivedC C_ob;

 

C_ob.izm = 10; // Це і є неоднозначність: який саме

// член izm маємо на увазі???

C_ob.jzm = 20;

C_ob.kzm = 30;

 

// І тут теж неоднозначність з членом izm.

C_ob.sum = C_ob.izm + C_ob.jzm + C_ob.kzm;

 

// І тут теж неоднозначність з членом izm.

cout << C_ob.izm << " ";

 

cout << C_ob.jzm << " " << C_ob.kzm << " ";

cout << C_ob.sum;

 

getch (); return 0;

}

Як зазначено у коментарях до цієї програми, обидва класи derivedA і derivedB успадковують клас baseClass. Але клас derivedC успадковує як клас derivedA, так і клас derivedB. У результаті в об'єкті типу derivedC присутні дві копії класу baseClass, тому у такому виразі

C_ob.izm = 20;

не зрозуміло, на яку саме копію члена izm тут дане посилання: на член, успадкований від класу derivedA або від класу derivedB? Оскільки в об'єкті C_ob є обидві копії класу baseClass, то у ньому існують і два члени C_ob.izm! Через те ця настанова izm є успадкуванням неоднозначним (істотно невизначеним).

Є два способи виправити попередню програму. Перший полягає у застосуванні оператора дозволу контексту (дозволи області видимості), за допомогою якого можна "вручну" вказати потрібного члена izm. Наприклад, наступна версія цієї програми успішно скомпілюється та виконається так, як очікувалося.

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

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class baseClass { // Оголошення базового класу

public:

int izm;

};

 

// Клас derivedA успадковує клас baseClass.

class derivedA: public baseClass {

public:

int jzm;

};

 

// Клас derivedB успадковує клас baseClass.

class derivedB: public baseClass {

public:

int kzm;

};

 

/* Клас derivedC успадковує обидва класи derivedA і derivedB.

Це означає, що у класі derivedC існує дві копії класу baseClass! */

class derivedC: public derivedA, public derivedB {

public:

int sum;

};

 

int main ()

{

derivedC C_ob;

 

C_ob.derivedA::izm = 10; // Контекст дозволений, використовується

// член izm класу derivedA.

C_ob.jzm = 20;

C_ob.kzm = 30;

 

// Контекст дозволений і тут.

C_ob.sum = C_ob.derivedA::izm + C_ob.jzm + C_ob.kzm;

 

// Виникнення неоднозначності ліквідована і тут.

cout << C_ob.derivedA::izm << " ";

cout << C_ob.jzm << " " << C_ob.kzm << " ";

cout << C_ob.sum;

 

getch (); return 0;

}

Застосування оператора "::" дає змогу програмі "ручним способом" вибрати версію класу baseClass (успадковану класом derivedA). Але після такого рішення виникає цікаве запитання: а що, коли насправді потрібна тільки одна копія класу baseClass? Чи можна якимсь чином запобігти включенню двох копій у клас derivedC? Відповідь, як, напевно, Ви здогадалися, позитивна. Це рішення досягається за допомогою віртуальних базових класів.

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

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

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

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

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class baseClass { // Оголошення базового класу

public:

int izm;

};

 

// Клас derivedA успадковує клас baseClass як віртуальний.

class derivedA: virtual public baseClass {

public:

int jzm;

};

 

// Клас derivedB успадковує клас baseClass як віртуальний.

class derivedB: virtual public baseClass {

public:

int kzm;

};

 

/* Клас derivedC успадковує обидва класи derivedA і derivedB.

Цього разу він містить тільки одну копію класу baseClass. */

class derivedC: public derivedA, public derivedB {

public:

int sum;

};

 

int main ()

{

derivedC C_ob;

 

C_ob.izm = 10; // Тепер неоднозначності немає.

C_ob.jzm = 20;

C_ob.kzm = 30;

 

// Тепер неоднозначності немає.

C_ob.sum = C_ob.izm + C_ob.jzm + C_ob.kzm;

 

// Тепер неоднозначності немає.

cout << C_ob.izm << " ";

 

cout << C_ob.jzm << " " << C_ob.kzm << " ";

cout << C_ob.sum;

 

getch (); return 0;

}

Як бачите, ключове слово virtual передує решті частини специфікації успадкованого класу. Тепер обидва класи derivedA і derivedB успадковують клас baseClass як віртуальний, і тому при будь-якому багаторазовому їх успадкуванні у похідний клас буде включено тільки одну його копію. Отже, у класі derivedC є тільки одна копія класу baseClass, а настанова C_ob.izm = 10 тепер є абсолютно допустимою і не містить ніякої неоднозначності.

Необхідно пам'ятати! Навіть якщо обидва класи derivedA і derivedB задають клас baseClass як virtual -клас, він, як і раніше, присутній в об'єкті будь-якого типу.

Наприклад, така послідовність настанов є цілком допустимою:

// Оголошення похідного класу типу derivedA.

derivedA myClass;

 

myClass.izm = 88;

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

15.8. Читання С++-графів успадкування

Іноді ієрархії С++-класів зображують графічно, що полегшує їх розуміння. Але деколи різні способи зображення графів успадкування класів вводять новачків у оману. Розглянемо, наприклад, ситуацію, у якій клас А успадковується класом В, який, своєю чергою, успадковується класом С. Використовуючи стандартну С++-систему позначень, цю ситуацію можна відобразити так, як це показано на рис. 15.1, а.

а) б)

Рис. 15.1. Графічне представлення ієрархії С++-класів успадкування

Як бачите, стрілки на цьому рисунку спрямовані вгору, а не вниз. Багато хто спочатку вважає такий напрям стрілок алогічним, але саме цей стиль прийнятий більшістю С++-програмістів. Згідно зі стильовою графікою C++, стрілка повинна вказувати на базовий клас Отже, стрілка означає "виведений з", а не "породжує". Розглянемо інший приклад, у якому спробуємо самостійно описати словами значення зображення, яке показано на рис. 15.1, б.

З цього графа виходить, що клас Е виведений з обох класів С і D. Іншими словами, клас Е має два базові класи С і D. При цьому клас С виведений з класу А, а клас D – з класу В. Незважаючи на те, що напрям стрілок спочатку може видатися для Вас незвичним, все ж таки краще познайомитися з цим стилем графічних позначень, оскільки він широко використовується у книгах, журналах і документації на компілятори.


Розділ 16. Віртуальні функції та поліморфізм

Одною з трьох основних особливостей об'єктно-орієнтованого програмування є поліморфізм. Стосовно мови програмування C++, під терміном поліморфізм розуміють опис процесу, у якому різні результати реалізації функції можуть бути доступні за допомогою одного і того ж імені. З цієї причини поліморфізм іноді характеризується фразою "один інтерфейс, багато методів". Це означає, що до всіх функцій-членів загального класу можна отримати доступ одним і тим самим способом, незважаючи на можливу відмінність у конкретних діях, пов'язаних з кожною окремою операцією.

Поліморфізм – це назва процесу, у якому різні результати реалізації функції доступні за допомогою одного її імені.

У мові програмування C++ поліморфізм підтримується як у процесі виконання, так у період компілювання програми. Перевантаження операторів і функцій – це приклади поліморфізму, що належить до моменту компілювання. Але, попри потужність механізму перевантаження операторів і функцій, він не у змозі вирішити всі завдання, які виникають в реальних додатках об'єктно-орієнтованої мови програмування. Тому у мові C++ також реалізований поліморфізм періоду виконання, який базується на використанні похідних класів і віртуальних функцій, що і становлять основні теми цього розділу.

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

16.1. Покажчики на похідні типи – підтримка динамічного поліморфізму

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

Покажчик на базовий клас може посилатися на будь-який об'єкт, який є виведеним з цього базового класу.

У мові програмування C++ покажчик на базовий клас також можна використовувати для посилання на об'єкт будь-якого класу, виведеного з базового. Наприклад, припустимо, що у нас є базовий клас bClass і клас dClass, який виведений з класу bClass. У мові програмування C++ будь-який покажчик, оголошений як покажчик на клас bClass, може бути також покажчиком на клас dClass. Отже, після цих оголошень

bClass *p; // Створення покажчика на об'єкт базового типу

bClass B_ob; // Створення об'єкта базового типу

dClass D_ob; // Створення об'єкта похідного типу

обидві такі настанови є абсолютно законними:

p = &В_оb; // Покажчик p вказує на об'єкт типу bClass

p = &D_ob; // Присвоєння адреси об'єкта похідного класу

/* Покажчик p вказує на об'єкт типу dClass, який є об'єктом,

що був виведений з класу bClass. */

У наведеному прикладі покажчик р можна використовувати для доступу до всіх елементів об'єкта D_ob, що є виведеним з об'єкта B_ob. Проте до елементів, які становлять специфічну "надбудову" (над базою, тобто над базовим класом bClass) об'єкта D_ob, доступ за допомогою покажчика р отримати не можна.

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

Код програми 16.1. Демонстрація механізму використання покажчиків на базовий клас для доступу до об'єктів похідних класів

#include <iostream> // Для потокового введення-виведення

#include <cstring> // Для роботи з рядковими типами даних

using namespace std; // Використання стандартного простору імен

 

class bClass { // Оголошення класового типу

char author[80];

public:

void putAuthor(char *s) { strcpy (author, s);}

void showAuthor() { cout << author << "\n";}

};

 

class dClass: public bClass { // Оголошення класового типу

char title[80];

public:

void putTitle(char *num) { strcpy (title, num);}

void showTitle() { cout << "Назва: "; cout << title << "\n";}

};

 

int main ()

{

bClass *p; // Створення покажчика на об'єкт базового типу

bClass B_ob; // Створення об'єкта базового типу

dClass *dp; // Створення покажчика на об'єкт похідного типу

dClass D_ob; // Створення об'єкта похідного типу

 

p = &B_ob; // Присвоєння адреси об'єкта базового класу

 

// Доступ до класу bClass через покажчик.

p->putAuthor("Еміль Золя|");

 

// Доступ до класу dClass через "базовий" покажчик.

p = &D_ob; // Присвоєння адреси об'єкта похідного класу

p->putAuthor("Вільям Шекспір");

 

// Покажемо, що кожен автор належить до відповідного об'єкта.

B_ob.showAuthor();

D_ob.showAuthor();

cout << "\n";

 

/* Оскільки функції putTitle() і showTitle() не є частиною

базового класу, то вони недоступні через "базовий" покажчик p,

і тому до них потрібно звертатися або безпосередньо, або,

як показано тут, через покажчик на похідний тип. */

 

dp = &D_ob; // Присвоєння адреси об'єкта похідного класу

dp->putTitle("Буря");

p->showAuthor(); // Тут можна використовувати або покажчик p,

// або покажчик dp.

dp->showTitle();

 

getch (); return 0;

}

У процесі виконання ця програма відображає на екрані такі результати:

Еміль Золя|

Вільям Шекспір

 

Вільям Шекспір

Назва: Буря

У наведеному прикладі покажчик р визначається як покажчик на клас bClass. Але він може також посилатися на об'єкт похідного класу dClass, причому його можна використовувати для доступу тільки до тих елементів похідного класу, які успадковані від базового. Проте необхідно пам'ятати, що через "базовий" покажчик неможливо отримати доступ до тих членів, які специфічні для похідного класу. Ось чому до функції showTitle() звернення реалізується за допомогою покажчика dp, який є покажчиком на похідний клас.

Якщо виникає потреба за допомогою покажчика на базовий клас отримати доступ до елементів, що визначені певним похідним класом, то необхідно привести цей покажчик до типу покажчика на похідний тип. Наприклад, у процесі виконання цього рядка коду програми дійсно буде викликано функцію showTitle() об'єкта D_ob:

((dClass *)p)->showTitle();

Зовнішній набір круглих дужок використовують для зв'язку операції приведення типу з покажчиком р, а не з типом, що повертається функцією showTitle(). Незважаючи на те, що у використанні такої операції формально немає нічого некоректного, такого запису, за змогою, необхідно уникати, оскільки усі ці прийоми просто вносять у код програми плутанину[65].

Крім того, необхідно розуміти: хоча "базовий" покажчик можна використовувати для доступу до об'єктів будь-якого похідного типу, зворотне ж твердження є неправильним. Іншими словами, використовуючи покажчик на похідний клас, не можна отримати доступ до об'єкта базового типу.

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

Той факт, що покажчик на базовий тип можна використовувати для посилання на будь-який об'єкт, який є виведеним з базового, надзвичайно важливий і принциповий для мови C++. Як буде показано далі, ця гнучкість є ключовим моментом для способу реалізації динамічного поліморфізму у мові програмування C++.

Посилання на похідні типи

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

16.2. Віртуальні функції

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

16.2.1. Поняття про віртуальні функції

Віртуальна функція – це функція, яка оголошується в базовому класі з використанням ключового слова virtual і перевизначається в одному або декількох похідних класах. Таким чином, кожен похідний клас може мати власну версію віртуальної функції. Цікаво розглянути ситуацію, коли віртуальна функція викликається через покажчик (або посилання) на базовий клас. У цьому випадку мова програмування C++ визначає, яку саме версію віртуальної функції необхідно викликати, за типом об'єкта, яка адресується цим покажчиком. Причому необхідно мати на увазі, що це рішення приймається у процесі виконання програми. Отже, при вказанні на різні об'єкти викликатимуться і різні версії віртуальної функції. Іншими словами, саме за типом об'єкта (а не за типом самого покажчика), яка адресується, визначається, яку версію віртуальної функції буде виконано. Таким чином, якщо базовий клас містить віртуальну функцію і якщо з цього базового класу виведено два (або більше) інші класи, то при адресації різних типів об'єктів через покажчик на базовий клас виконуватимуться і різні версії віртуальної функції. Аналогічний механізм працює і під час використання посилання на базовий клас.

Щоб оголосити функцію віртуальною, достатньо, щоб її оголошенню передувало ключове слово virtual.

Функція оголошується віртуальною в базовому класі за допомогою ключового слова virtual. Під час перевизначення віртуальної функції у похідному класі ключове слово virtual повторювати не потрібно (хоча це не буде помилкою).

Клас, який містить віртуальну функцію, називається поліморфним класом.

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

Код програми 16.2. Демонстрація механізму використання віртуальних функцій

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class baseClass { // Оголошення базового класу

public:

// Оголошення віртуальної функції

virtual void who() { cout << "Базовий клас.\n";}

};

class firstD: public baseClass {

public:

// Перевизначення функції who() для класу firstD.

void who() { cout << "Перший похідний клас.\n";}

};

class secondD: public baseClass {

public:

// Перевизначення функції who() для класу secondD.

void who() { cout << "Другий похідний клас.\n";}

};

 

int main ()

{

baseClass Base_ob; // Створення об'єкта базового типу

baseClass *p; // Створення покажчика на об'єкт базового типу

firstD First_ob; // Створення об'єкта похідного типу

secondD Second_ob; // Створення об'єкта похідного типу

 

p = &Base_ob; // Присвоєння адреси об'єкта базового класу

p->who(); // Доступ до функції who() класу baseClass

 

p = &First_ob; // Присвоєння адреси об'єкта похідного класу

p->who(); // Доступ до функції who() класу firstD

 

p = &Second_ob; // Присвоєння адреси об'єкта похідного класу

p->who(); // Доступ до функції who() класу secondD




Поделиться с друзьями:


Дата добавления: 2014-11-29; Просмотров: 421; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.172 сек.