Студопедия

КАТЕГОРИИ:


Архитектура-(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 8 страница




 

cout << а + b;

 

getch (); return 0;

}

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

C>add 100.2 231

7.3.2. Перетворення числових рядків у числа

Стандартна бібліотека C++ містить декілька функцій, які дають змогу перетворити рядкове представлення числа в його внутрішній формат. Для цього використовуються такі функції, як atoi (), atol () і atof (). Вони перетворять рядок в цілочисельне значення (типу int), довге ціле (типу long) і значення з плинною крапкою (типу double) відповідно. Використання цих функцій (для їх виклику необхідно приєднати до програми заголовний файл < cstdlib >) продемонстровано у наведеному нижче коді програми.

Код програми 7.15. Демонстрація механізму використання функцій atoi(), atol() і atof()

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

#include <cstdlib> // Для використання бібліотечних функцій

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

 

int main ()

{

int izm;

long jzm;

double kzm;

 

izm = atoi ("100");

jzm = atol ("100000");

kzm = atof ("-0.123");

cout << izm << " " << jzm << " " << kzm;

cout << "\n";

 

getch (); return 0;

}

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

100 100000 -0.123

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

7.4. Використання у функціях настанови return

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

7.4.1. Завершення роботи функції

Як зазначалося вище, керування від функції передається ініціатору її виклику в двох ситуаціях: або внаслідок виявлення фігурної дужки, що закривається, або у процесі виконання настанови return. Настанову return можна використовувати з певним заданим значенням або без нього. Але, якщо в оголошенні функції вказано тип значення (тобто не тип void), що повертається, то функція повинна повертати значення цього типу. Тільки void -функції можуть використовувати настанову return без будь-якого значення.

Для void -функцій настанова return в основному використовується як елемент програмного керування. Наприклад, у наведеній нижче функції виводиться результат зведення числа в позитивний цілочисельний степінь. Якщо ж показник степеня виявиться негативним, настанова return забезпечить вихід з функції, перш ніж буде зроблена спроба обчислити такий вираз. У цьому випадку настанова return діє як керівний елемент, тобто запобігає небажаному виконанню певної частини функції.

void power(int base, int exp)

{

int izm;

 

if (exp<0) return; /* Щоб не допустити зведення числа в негативний

степінь, тут виконується повернення у функцію, яка

викликає, і ігнорується решта частини функції. */

izm = 1;

for (; exp; exp--) izm = base * izm;

cout << "Результат дорівнює: " << izm;

}

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

void fun()

{

//...

switch (с) {

case 'a': return;

case 'b': //...

case 'c': return;

}

if (count<100) return; //...

}

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

7.4.2. Повернення значень

Кожна функція, окрім типу void, повертає яке-небудь значення. Це значення безпосередньо задається за допомогою настанови return. Іншими словами, будь-яку не void -функцію можна використовувати як операнд у виразі. Отже, кожний з наступних виразів допустимо у мові програмування C++:

х = power(y);

 

if (max(х, y)) > 100) cout << "більше";

 

switch (abs (х)) {

Незважаючи на те, що всі не void -функції повертають значення, вони необов'язково мають бути використані у програмі. Найпоширеніше запитання щодо значень, які повертаються функціями, звучить так: "Оскільки функція повертає певне значення, то хіба я не повинен (повинна) присвоїти це значення будь-якій змінній?". Відповідь: ні, це необов'язково. Якщо значення, що повертається функцією, не бере участі в операції присвоєння, воно просто відкидається (втрачається).

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

Код програми 7.16. Демонстрація механізму використання стандартної бібліотечної функції abs()

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

#include <cstdlib> // Для використання бібліотечних функцій

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

 

int main ()

{

int izm;

 

izm = abs (-10); // рядок 1

cout << abs (-23); // рядок 2

abs (100); // рядок 3

 

getch (); return 0;

}

Функція abs () повертає абсолютне значення свого цілочисельного аргументу. Вона використовує заголовок < cstdlib >. У рядку 1 значення, що повертається функцією abs (), присвоюється змінній izm. У рядку 2 значення, що повертається функцією abs (), нічому не присвоюється, але використовується настановою cout. Нарешті, в рядку 3 значення, що повертається функцією abs (), втрачається, оскільки не присвоюється ніякій іншій змінній і не використовується як частина виразу.

Якщо функція, тип якої є відмінним від типу void, завершується внаслідок виявлення фігурної дужки, що закривається, то значення, яке вона повертає, не визначене (тобто невідоме). Через особливості формального синтаксису C++ не void -функція не зобов'язана виконувати настанову return. Це може відбутися у тому випадку, якщо кінець функції буде досягнуто до виявлення настанови return. Але, оскільки функція оголошена як така, що повертає значення, значення буде таки повернено, навіть якщо це просто "сміття". У загальному випадку не кожна зі створюваних Вами void -функцій повинна повертати значення за допомогою безпосередньо виконуваної настанови return.

Вище згадувалося, що void -функція може мати декілька настанов return. Те саме стосується і функцій, які повертають значення. Наприклад, представлена у наведеному нижче коді програми функція find _substr() використовує дві настанови return, які дають змогу спростити алгоритм її роботи. Ця функція виконує пошук заданого підрядка в заданому рядку. Вона повертає індекс першого виявленого входження заданого підрядка або значення -1, якщо заданий підрядок не було знайдено. Наприклад, якщо в рядку "Я люблю C++" необхідно відшукати підрядок "люблю", то функція find _substr() поверне число 2 (яке є індекс символу "л" у рядку "Я люблю C++").

Код програми 7.17. Демонстрація механізму використання двох настанов return, які значно спрощують алгоритм її роботи

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

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

 

int find _substr(char *sub, char *str);

 

int main ()

{

int index;

 

index = find _substr("три", "один два три чотири");

cout << "Індекс дорівнює " << index; // Індекс дорівнює 9.

 

getch (); return 0;

}

 

// Функція повертає індекс шуканого підрядка або -1,

// якщо його не було знайдено.

int find _substr(char *sub, char *str)

{

int t;

char *p, *p2;

 

for (t=0; str[t]; t++) {

p = &str[t]; // Встановлення покажчиків

p2 = sub;

while (*p2 && *p2==*p) { // перевірка збігу

p++;

p2++;

}

/* Якщо досягнуто кінець р2-рядка (тобто підрядка)

то підрядок було знайдено. */

if (!*p2) return t; // Повертаємо індекс підрядка.

}

 

return -1; // Підрядок не був виявлений.

}

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

Індекс дорівнює 9

Оскільки шуканий підрядок існує в заданому рядку, виконується перша настанова return. Для прикладу змінимо програму так, щоб нею виконувався пошук підрядка, який не є частиною заданого рядка. У цьому випадку функція find _substr() повинна повернути значення -1 (завдяки другій настанові return).

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

7.4.3. Функції, які не повертають значень (void-функції)

Як Ви помітили, функції, які не повертають значень, оголошуються з вказанням типу void. Це ключове слово не допускає їх використання у виразах і захищає від невірного застосування. У наведеному нижче прикладі функція print_vertical() виводить аргумент командного рядка у вертикальному напрямі (вниз) по лівому краю екрана. Оскільки ця функція не повертає ніякого значення, в її оголошенні використано ключове слово void.

Код програми 7.18. Демонстрація виведення аргументу командного рядка у вертикальному напрямі (вниз) по лівому краю екрана

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

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

 

void print_vertical(char *str);

 

int main (int argc, char * argv [])

{

if (argc ==2) print_vertical(argv [1 ]);

 

getch (); return 0;

}

 

void print_vertical(char *str)

{

while (*str) cout << *str++ << "\n";

}

Оскільки print_vertical() оголошена як void -функція, то її не можна використовувати у виразі. Наприклад, наступна настанова неправильна і тому не скомпілюється:

х = print_vertical("Привіт!"); // помилка

Варто знати! У перших версіях мови C не було передбачено типу void. Таким чином, у старих С-програмах функції, які не повертають значень, за замовчуванням мали тип int. Якщо Вам доведеться натрапити на такі функції у процесі перекладу старих С-програм "на рейки" C++, просто оголосіть їх з використанням ключового слова void, зробивши їх void -функціями.

7.4.4. Функції, які повертають покажчики

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

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

int *fun();

Якщо функція повертає покажчик, то значення, що використовується в її настанові return, також повинно бути покажчиком[30].

У наведеному нижче коді програми продемонстровано механізм використання покажчика як типу значення, що повертається. Це нова версія наведеної вище функції find _substr(), тільки тепер вона повертає не індекс знайденого підрядка, а покажчик на неї. Якщо заданий підрядок не знайдений, повертається нульовий покажчик.

Код програми 7.19. Демонстрація нової версії функції find_substr(), яка повертає покажчик на підрядок

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

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

 

char * find _substr(char *sub, char *str);

 

int main ()

{

char *substr;

 

substr = find _substr("три", "один два три чотири");

cout << "Знайдений підрядок: " << substr;

 

getch (); return 0;

}

// Функція повертає покажчик на шуканий підрядок або нуль,

// якщо такий не буде знайдено.

char * find _substr(char *sub, char *str)

{

int t;

char *p, *p2, *start;

 

for (t=0; str[t]; t++) {

p = &str[t]; // Встановлення покажчиків

start = p;

p2 = sub;

while (*p2 && *p2==*p) { // Перевірка збігу

p++;

p2++;

}

 

// Якщо досягнуто кінець р2-підрядка,

// то цей підрядок буде знайдено.

if (!*р2) return start; // Повертаємо покажчик на початок

// знайденого підрядка.

}

getch (); return 0; // Підрядок не знайдено

}

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

Знайдений підрядок: три чотири

У цьому випадку, коли підрядок "три" було знайдено в рядку "один два три чотири", функція find _substr() повернула покажчик на початок шуканого підрядка "три", який у функції main () був присвоєний змінній substr. Таким чином, під час виведення значення substr на екрані відобразився залишок рядка, тобто " три чотири".

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

7.4.5. Прототипи функцій

Прототип оголошує функцію до її першого використання.

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

● тип значення, що повертається нею;

● тип її параметрів;

● кількість параметрів.

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

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

● вони дають змогу C++ виявити неприпустимі перетворення типів аргументів, що використовуються під час виклику функції, в тип, який було вказано в оголошенні її параметрів, і повідомити про них;

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

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

type func_name (type parm_name1, type parm_name2,., type parm_nameN);

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

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

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

void sqr_fun(int *izm); // прототип функції

 

int main ()

{

int x;

x = 10;

sqr_fun(x); // *** Помилка *** -- невідповідність типів!

 

getch (); return 0;

}

 

void sqr_fun(int *izm)

{

*izm = *izm * *izm;

}

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

7.4.6. Грунтовніше про заголовки у C++-програмах

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

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

Порівняння старого і нового стилів оголошення параметрів функцій

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

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

float fun(int a, int b, char ch)

{...

виглядатиме з використанням старого стилю дещо по-іншому.

float fun(а, b, ch)

int a, b;

char ch;

{...

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

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

7.4.7. Організація рекурсивних функцій

Рекурсивна функція – це функція, яка викликає сама себе.

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

Класичним прикладом рекурсії є обчислення факторіалу від числа за допомогою функції factr(). Факторіал числа N є добуток всіх цілих чисел від 1 до N. Наприклад, факторіал числа 3 дорівнює 1´2´3, або 6. Рекурсивний спосіб обчислення факторіалу від числа продемонстровано у наведеному нижче коді програми. Для порівняння сюди ж включений і його нерекурсивний (ітеративний) еквівалент.

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

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

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

 

int factr(int n);

int fact(int n);

 

int main ()

{

// Використання рекурсивної версії.

cout << "Факторіал числа 4 дорівнює " << factr(4) << "\n";

// Використання ітеративної версії.

cout << "Факторіал числа 4 дорівнює " << fact(4) << "\n";

 

getch (); return 0;

}

 

// Рекурсивна версія.

int factr(int n)

{

int rezult;

if (n == 1) return (1);

rezult = factr(n-1)*n;

 

return (rezult);

}

 

// Ітеративна версія.

int fact(int n)

{

int t, rezult;

 

rezult = 1;

for (t=1; t<=n; t++) rezult = rezult*(t);

 

return (rezult);

}

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

Рекурсивна функція factr() є дещо складнішою. Якщо вона викликається з аргументом, що дорівнює 1, то відразу повертає значення 1. В іншому випадку вона повертає добуток factr(n-l)*n. Для обчислення цього виразу викликається метод factr() з аргументом n-1. Цей процес повторюється доти, доки аргумент не стане таким, що дорівнює 1, після чого викликані раніше методи почнуть повертати значення. Наприклад, під час обчислення факторіалу від числа 2 перше звернення до методу factr() приведе до другого звернення до того ж методу, але з аргументом, що дорівнює 1. Другий виклик методу factr() поверне значення 1, яке буде помножене на 2 (початкове значення параметра n). Можливо, Вам буде цікаво вставити у функцію factr() настанову cout, щоб показати ієрархічний рівень кожного виклику і проміжні результати.

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

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

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

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

Розглянемо ще один приклад рекурсивної функції. Функція reverse () використовує рекурсію для відображення свого рядкового аргументу в зворотному порядку.

Код програми 7.22. Демонстрація відображення рядка в зворотному порядку за допомогою рекурсії




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


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


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



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




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