Студопедия

КАТЕГОРИИ:


Архитектура-(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)

Auto, extern, register, static, mutable 9 страница




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

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

void reverse (char *s);

 

int main ()

{

char strMas[] = "Це тест";

reverse (strMas);

 

getch (); return 0;

}

 

// Виведення рядка в зворотному порядку.

void reverse (char *s)

{

if (*s) reverse (s+1);

else return;

cout << *s;

}

Функція reverse () перевіряє, чи не переданий їй як параметр покажчик на нуль, яким завершується рядок. Якщо ні, то функція reverse () викликає саму себе з покажчиком на наступний символ у рядку. Цей "закручений" процес повторюється доти, доки тієї ж самої функції не буде передано покажчик на нуль. Коли, нарешті, виявиться символ кінця рядка, розпочнеться процес "розкручування", тобто викликані раніше функції почнуть повертати значення, і кожне повернення супроводжуватиметься "довиконанням" методу, тобто відображенням символу s. Внаслідок цього початковий рядок посимвольно відобразиться в зворотному порядку.

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

 


Розділ 8. Використання засобів програмування для розширення можливостей С++-функцій

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

Оскільки до параметрів функцій часто застосовуються посилання (це основна причина їх існування), почнемо цей розділ з короткого перегляду способів передачі аргументів функціям.

8.1. Способи передачі аргументів функціям

Щоб зрозуміти походження посилання, необхідно знати теорію процесу передачі аргументів функціям. У загальному випадку в мовах програмування, як правило, передбачається два способи, які дають змогу передавати аргументи в підпрограми (функції, методи, процедури). Перший називається викликом за значенням (call-by-value). У цьому випадку значення аргументу копіюється у формальний параметр підпрограми. Значить зміни, внесені в параметри підпрограми, не впливають на аргументи, що використовуються під час її виклику.

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

Другий спосіб передачі аргументу підпрограмі називається викликом за посиланням (call-by-reference). У цьому випадку в параметр копіюється адреса аргументу (а не його значення). У межах підпрограми, що викликається, цю адресу використовують для доступу до реального аргументу, що задається під час її виклику. Це означає, що зміни, внесені в параметр, нададуть дію на аргумент, що використовується під час виклику підпрограми.

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

8.1.1. Механізм передачі аргументів у мові програмування C++

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

Розглянемо таку функцію.

Код програми 8.1. Демонстрація механізму передачі аргументів у мові програмування C++

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

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

 

int sqr_fun(int x);

 

int main ()

{

int t=10;

cout << "t^2=" << sqr_fun(t) << "; t=" << t;

 

getch (); return 0;

}

 

int sqr_fun(int x)

{

x = x*x;

return x;

}

У наведеному прикладі значення аргументу 10, що передається функції sqr_fun(), копіюється в параметр х. У процесі її виконання присвоєння х = х*x змінюється тільки локальній змінній х. Змінна t, використовувана під час виклику функції sqr_fun(), як і раніше, матиме значення 10 і на неї ніяк не вплинуть операції, що виконуються у цій функції. Отже, після запуску цієї програми на екран монітора буде виведено такий результат: t^2=100; t=10.

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

8.1.2. Використання покажчика для забезпечення виклику за посиланням

Незважаючи на те, що як С++-узгодження про передачу параметрів за замовчуванням діє виклик за значенням, існує можливість "вручну" замінити його викликом за посиланням. У цьому випадку функції передаватиметься адреса аргументу (тобто покажчик на аргумент). Це дасть змогу внутрішньому коду функції змінити значення аргументу, яке зберігається поза функцією. Приклад такого "дистанційного" керування значеннями змінних було показано в попередньому розділі під час перегляду можливості виклику функції з покажчиками (у прикладі коду програми функції передавався покажчик на цілочисельну змінну). Як уже зазначалося вище, покажчики передаються функціям подібно до значень будь-якого іншого типу. Безумовно, для цього необхідно оголосити параметри з типом покажчиків.

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

void swap(int *x, int *y)

{

int tmp;

tmp = *x; // Тимчасово зберігаємо значення, розташоване за адресою х.

*x = *y; // Поміщаємо значення, що зберігається

// за адресою y, за адресою х.

*y = tmp; // Поміщаємо значення, яке раніше зберігалося

// за адресою х, за адресою у.

}

Тут параметри *x і *y означають змінні, що адресуються покажчиками x і y, які просто є адресами аргументів, що використовуються під час виклику функції swap(). Отже, у процесі виконання цієї функції буде зроблено реальний обмін вмістом змінних, що використовуються під час її виклику.

Оскільки функція swap() чекає, щоб отримати два покажчики, то програміст повинен пам'ятати, що функцію swap() необхідно викликати з адресами змінних, значення яких він хочете обміняти. Коректний виклик цієї функції продемонстровано у наведеному нижче коді програми.

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

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

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

 

// Оголошуємо функцію swap(), яка використовує покажчики.

void swap(int *x, int *y);

 

int main ()

{

int izm, jzm;

 

izm = 10; jzm = 20;

cout << "Початкові значення змінних izm та jzm: ";

cout << izm << " " << jzm << "\n";

swap(&jzm, &izm);//Викликаємо swap() з адресами змінних izm та jzm.

cout << "Значення змінних izm та jzm після обміну: ";

cout << izm << " " << jzm << "\n";

 

getch (); return 0;

}

 

// Обмін аргументами.

void swap(int *x, int *y)

{

int tmp;

tmp = *x; // Тимчасово зберігаємо значення за адресою х.

*x = *y; // Поміщаємо значення, збережене раніше за адресою y,

// за адресою х.

*y = tmp; // Поміщаємо значення, яке раніше зберігалося

// за адресою х, за адресою у.

}

Результати виконання цієї програми є такими:

Початкові значення змінних izm та jzm: 10 20

Значення змінних izm та jzm після обміну: 20 10

У наведеному прикладі змінній izm було присвоєне початкове значення 10, а змінній jzm – 20. Потім була викликана функція swap() з адресами змінних izm та jzm. Для отримання адрес тут використовується унарний оператор "&". Отже, функції swap() під час виклику були передані адреси змінних izm та jzm, а не їх значення. Після завершення роботи функції swap() змінні izm та jzm обмінялися своїми значеннями.

8.2. Посилальні параметри

Незважаючи на можливість "вручну" організувати виклик за посиланням за допомогою оператора отримання адреси, такий підхід не завжди є зручним. По-перше, він вимушує програміста виконувати всі операції з використанням покажчиків. По-друге, викликаючи функцію, програміст повинен не забути передати їй адреси аргументів, а не їхнє значення. На щастя, у мові програмування C++ можна зорієнтувати компілятор на автоматичне використання виклику за посиланням (замість виклику за значенням) для одного або декількох параметрів конкретної функції. Така можливість реалізується за допомогою посилального параметра (reference parameter). Під час використання посилального параметра функції автоматично передається адреса (а не значення) аргументу. У процесі виконання коду функції, а саме у процесі вико­нан­ня операцій над посилальним параметром, забезпечується його автоматичне перейменування, і тому програмісту не потрібно використовувати оператори, що працюють з покажчиками.

Посилальний параметр автоматично отримує адресу відповідного аргументу.

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

8.2.1. Механізм дії посилальних параметрів

Щоб краще зрозуміти механізм дії посилальних параметрів, розглянемо спершу простий приклад. У наведеному нижче коді програми функція fun() приймає один посилальний параметр типу int.

Код програми 8.3. Демонстрація механізму дії посилального параметра

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

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

void fun(int &izm);

 

int main ()

{

int num = 1;

cout << "Старе значення змінної num: " << num << "\n";

 

fun(num); // Передаємо адресу змінної num функції fun().

cout << "Нове значення змінної num: " << num << "\n";

 

getch (); return 0;

}

 

void fun(int &izm)

{

izm = 10; // Модифікування аргументу, що задається

// під час його виклику.

}

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

Старе значення змінної num: 1

Нове значення змінної num: 10

Зверніть особливу увагу на визначення функції fun():

void fun(int &izm)

{

izm = 10; // Модифікування аргументу, що задається

// під час його виклику.

}

Отже, розглянемо оголошення параметра izm. Його імені передує символ "&", який "перетворює" змінну izm на посилальний параметр[31]. Настанова

izm = 10;

(у цьому випадку вона одна утворює тіло функції) не присвоює змінній izm значення 10. Насправді значення 10 присвоюється змінній, на яку посилається змінна izm (у нашій програмі нею є змінна num). Звернемо Вашу увагу на те, що у цій настанові не використовують оператор "*", який є необхідним під час роботи з покажчиками. Застосовуючи посилальний параметр, Ви тим самим повідомляєте С++-компілятор про передачу адреси (тобто покажчика), і компілятор автоматично перейменовує його за Вас. Понад це, якби Ви спробували "допомогти" компіляторові, використавши оператора "*", то відразу ж отримали б повідомлення про помилку (і справді "жодна добра справа не залишається безкарною").

Оскільки змінна izm була оголошена як посилальний параметр, компілятор автоматично передає функції fun() адресу будь-якого аргументу, з яким викликається ця функція. Таким чином, у функції main () настанова

fun(num); // Передаємо адресу змінної num функції fun().

передає функції fun() адресу змінної num (а не її значення). Звернемо Вашу увагу на те, що під час виклику функції fun() не потрібно передувати змінній num оператором "&"[32]. Оскільки функція fun() отримує адресу змінної num у формі посилання, вона може модифікувати значення цієї змінної.

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

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

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

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

 

// Оголошуємо функцію swap() з використанням посилальних параметрів.

void swap(int &x, int &y);

 

int main ()

{

int izm, jzm;

 

izm = 10;

jzm = 20;

cout << " Початкові значення змінних izm та jzm: ";

cout << izm << " " << jzm << "\n";

swap(jzm, izm);

cout << " Значення змінних izm та jzm після обміну: ";

cout << izm << " " << jzm << "\n";

 

getch (); return 0;

}

// Обмін аргументами.

void swap(int &x, int &y)

{

int tmp;

tmp = x; // Зберігаємо значення, розташоване за адресою х.

х = y; // Поміщаємо значення, що зберігається за адресою y

// за адресою х.

y = tmp; // Поміщаємо значення, яке раніше зберігалося

// за адресою х, за адресою y.

}

У цій програмі функція swap() визначається з розрахунку на виклик за посиланням, а не на виклик за значенням. Тому вона може виконати обмін значеннями двох аргументів, з якими вона викликається. Знову ж таки, звернемо Вашу увагу на те, що оголошення х і y посилальними параметрами позбавляє Вас від потреби використання оператора "*" при організації обміну значеннями. Як уже згадувалося вище, така "нав'язливість" з Вашого боку стала б причиною помилки. Тому запам'ятаєте, що компілятор автоматично генерує адреси аргументів, що використовуються під час виклику функції swap(), і автоматично перейменовує посилання x і y.

Отже, підведемо деякі підсумки. Після створення посилальний параметр автоматично посилається (тобто опосередковано вказує) на аргумент, що використовується під час виклику функції. Понад це, під час виклику функції не потрібно застосовувати до аргументу оператор "&". Окрім того, в тілі функції посилальний параметр використовується безпосередньо, тобто без використання оператора "*". Всі операції, що містять посилальний параметр, автоматично виконуються над аргументом, що використовується під час виклику функції.

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

8.2.2. Варіанти оголошень посилальних параметрів

У виданій в 1986 р. книзі " Язык программирования C++" (у якій був вперше описаний синтаксис мови C++) Б'єрн Страуструп представив стиль оголошення посилальних параметрів, який було схвалено іншими програмістами. Відповідно до цього стилю оператор "&" зв'язується з іменем типу, а не з іменем змінної. Наприклад, ось як виглядає ще один спосіб запису прототипу функції swap().

void swap(int & x, int& y);

Неважко помітити, що у цьому оголошенні символ "&" прилягає впритул до імені типу int, а не до імені змінної х.

Деякі програмісти визначають в такому стилі і покажчики, пов'язуючи символ "*" з типом, а не із змінною, як у наведеному прикладі:

float * р;

Наведені оголошення відображають бажання деяких програмістів мати у мові програмування C++ окремий тип посилання або покажчика. Але йдеться про те, що таке зв'язування символу "&" або "*" з типом (а не з іменем змінної) не поширюється на весь перелік змінних, які наводяться в оголошенні, що може призвести до плутанини. Наприклад, в наступному оголошенні створюється один покажчик (а не два) на цілочисельну змінну:

int * а, b;

У цьому записі b оголошується як цілочисельна змінна (а не як покажчик на цілочисельну змінну), оскільки, як визначено синтаксисом мови C++, використаний в оголошенні символ "*" або "&" пов'язується з конкретною змінною, якій він передує, а не з типом, за яким його знаходять.

Важливо розуміти, що для С++-компілятора абсолютно байдуже, як саме Ви напишете оголошення: int *p або int * р. Таким чином, якщо Ви вважаєте за краще пов'язувати символ "*" або "&" з типом, а не змінною, то вчиняйте так, як Вам зручно. Але, щоб уникнути надалі будь-яких непорозумінь, у цьому навчальному посібнику ми пов'язуватимемо символ "*" або "&" з іменем змінної, а не з іменем типу.

Варто знати! У мові С посилання не підтримуються. Тому єдиний спосіб забезпечити у мові С виклик за посиланням полягає у використанні покажчиків, як це було показано вище (див. першу версію функції swap()). Перетворюючи С-код в С++-код, Вам варто замість параметрів-покажчиків використовувати, де це можливо, посилання.

8.2.3. Повернення посилань

Функція може повертати посилання. У програмуванні мовою C++ передбачено декілька застосувань для посилальних значень, що повертаються функціями. Зараз ми продемонструємо тільки деякі з них, а інші розглянемо нижче у цьому навчальному посібнику, коли познайомимося з перевантаженням операторів (див. розд. 13).

Якщо функція повертає посилання, це означає, що вона повертає неявний покажчик на значення, що передається нею настановою return. Цей факт відкриває вражаючі можливості: функцію, виявляється, можна використовувати в лівій частині настанови присвоєння! Наприклад, розглянемо таку просту програму.

Код програми 8.5. Демонстрація повернення посилання

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

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

double &fun();

double num = 100.0;

 

int main ()

{

double newval;

cout << fun() << "\n"; // Відображаємо значення num.

 

newval = fun(); // Присвоюємо значення num змінною newval.

cout << newval << "\n"; // Відображаємо значення newval.

fun() = 99.1; // Змінюємо значення num.

cout << fun() << "\n"; // Відображаємо нове значення num.

 

getch (); return 0;

}

double &fun()

{

return num; // Повертаємо посилання на num.

}

Ось як виглядають результати виконання цієї програми:

99.1

Розглянемо цю програму грунтовніше. Судячи з прототипу функції fun(), вона повинна повертати посилання на double -значення. За оголошенням функції fun() знаходиться оголошення глобальної змінної num, яка ініціалізувалася значенням 100. У процесі виконання такої настанови виводиться початкове значення змінної num:

cout << fun() << "\n"; // Відображаємо значення num.

Після виклику функція fun() повертає посилання на змінну num. Оскільки функція fun() оголошена з "зобов'язанням" повернути посилання, то у процесі виконання рядка

return num; // Повертаємо посилання на num.

автоматично повертається посилання на глобальну змінну num. Це посилання потім використовується настановою cout для відображення значення num.

У процесі виконання рядка

newval = fun(); // Присвоюємо значення num змінній newval.

посилання на змінну num, що повертається функцією fun(), використовують для присвоєння значення num змінній newval.

А ось найцікавіший рядок у програмі

fun() = 99.1; // Змінюємо значення num.

У процесі виконання цієї настанови присвоєння значення змінній num дорівнює числу 99,1. І ось чому: оскільки функція fun() повертає посилання на змінну num, то це посилання і є приймачем настанови присвоєння. Таким чином, значення 99,1 присвоюється змінній num опосередковано, через посилання на неї, яке повертає функція fun().

Нарешті, у процесі виконання рядка

cout << fun() << "\n"; // Відображаємо нове значення num.

відображається нове значення змінної num (після того, як посилання на змінну num буде повернено внаслідок виклику функції fun() у настанові cout).

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

Код програми 8.6. Демонстрація повернення функцією значення посилального типу

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

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

double &change_fun(int izm); // Функція повертає посилання.

double vals[] = {1.1, 2.2, 3.3, 4.4, 5.5};

 

int main ()

{

int i;

cout << "Ось початкові значення: ";

for (i=0; i<5; i++) cout << vals[i] << " ";

cout << "\n";

 

change_fun(1) = 5298.23; // Змінюємо значення 2-го елемента масиву.

change_fun(3) = -98.8; // Змінюємо значення 4-го елемента масиву.

cout << "Ось змінені значення: ";

for (i=0; i<5; i++) cout << vals[i] << " ";

cout << "\n";

 

getch (); return 0;

}

double &change_fun(int i)

{

return vals[i]; // Повертаємо посилання на i-й елемент.

}

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

Ось початкові значення: 1.1 2.2 3.3 4.4 5.5

Ось змінені значення: 1.1 5298.23 3.3 -98.8 5.5

Давайте з'ясуємо, як вони були отримані. Функція change_fun() оголошена як та, що повертає посилання на значення типу double. Кажучи конкретніше, вона повертає посилання на елемент масиву vals, який задано їй як параметр i. Таким чином, у процесі виконання такої настанови функції main ()

change_fun(1) = 5298.23; // Змінюємо значення 2-го елемента масиву.

функція change_fun() повертає посилання на елемент vals[1]. Через це посилання елементові масиву vals[1] тепер присвоюється значення 5298,23. Аналогічні дії відбуваються у процесі виконання і цієї настанови:

change_fun(3) = -98.8; // Змінюємо значення 4-го елемента масиву.

Оскільки функція change_fun() повертає посилання на конкретний елемент масиву vals, то її можна використовувати в лівій частині настанови для присвоєння нового значення відповідному елементу масиву.

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

// Тут помилка: не можна повертати посилання на локальну змінну.

int &fun()

{

int izm = 10;

return izm;

}

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

8.2.4. Створення обмеженого (безпечного) масиву

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

Код програми 8.7. Демонстрація організації безпечного масиву

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

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

int & put (int i); // Поміщаємо значення в масив.

int get (int i); // Зчитуємо значення з масиву.

int vals[10];

int error = -1;

 

int main ()

{

put (0) = 10; // Поміщаємо значення в масив.

put (1) = 20;

put (9) = 30;

cout << get (0) << "\n";

cout << get (1) << "\n";

cout << get (9) << "\n";

 

// А зараз спеціально генеруємо помилку.

put (12) = 1; // Індекс за межами масиву.

 

getch (); return 0;

}

 

// Функція занесення значення в масив.

int & put (int i)




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


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


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



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




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